Bliki 3.0

From Organic Design wiki
Revision as of 19:41, 27 July 2013 by Nad (talk | contribs)
Bliki 2.0.jpg

I recently worked on a job that required a blog-like functionality to be done in the wiki. I started off basing the system on my Bliki 1.0 method, but started to run into problems implementing a tagging solution (having a tags category of categories, and having each tag page contain a blog-roll query for items of that tag. I got the system to work, but it was messy because the DynamicPageList (DPL) extension that the Bliki functionality is based on ignores includeonly tags and therefore caused all the pages containing blog-roll queries to become categorised into all the tag categories. These query pages could be filtered out of the query results, but it wasn't elegant.

The site I was implementing this functionality on uses Semantic MediaWiki (SMW) for a lot of its other functionality, so I decided to see if I could implement the Bliki functionality with SMW as well and drop DPL all together. This worked out very well and didn't have any messy categorisation problems and was overall a lot more efficient as well.

Note that this method is not really "semantic" because it doesn't make use of properties or types at all, it's purely category-based, but it's SMW that's doing all the work to query these categories and render the results in a way that produces the desired functionality. Following is a description of all the aspects required to reproduce the functionality on other MediaWiki installations.

Environment

The method required a few extensions other than just Semantic MediaWiki (and Validator that SMW depends on).

The following configuration in LocalSettings.php are needed as well (this is in addition to the inclusion of the extensions and their default configuration options).

$wgRawHtml = true;
$wgPFEnableStringFunctions = true;
$smwgQSubcategoryDepth = 0;
$smwgPageSpecialProperties = array( '_CDAT', '_LEDT' );
  • Note1: the $wgRawHtml global has been set to create the blog post form. This is option is only safe if the wiki doesn't allow public editing, otherwise something like HTMLets should be used instead.
  • Note2: $smwgQSubcategoryDepth being set to zero is of particular importance in our installation since the blog functionality must only return direct category members.
  • Note3: $smwgPageSpecialProperties is added to include the creation and last editor information in an article's semantic properties.
  • Note4: A function needs to be added to the UnkownAction hook to for processing the blog-post form. This is explained in detail below.

Blog post form

The easiest place to start with the description of blog functionality is with the post form since that's where it all begins from a users perspective. If the wiki was running the Semantic Forms extension, then that may well be the best approach to constructing a form to create the blog posts. But in this example I'm making a custom form with raw HTML (Note: $wgRawHTML should only ever be used on wikis that don't allow public editing! if you allow untrusted users to edit, then using something like HTMLets of Widgets would be a better approach).

Below is an example of a form created using raw HTML. The form includes a section for tags which are just articles that are members of a "Tags" category. Tags will be described in more detail below, but what we have here in this form regarding tags is an #ask query that calls a template called Checkbox for every one of the members in Category:Tags. Here's the content of the form page which uses the #tag parser-function to allow us to construct the content of an html tag that contains other parser-functions and templates. The REQUEST magic word is from the ExtraMagic extension and returns the specified query-string or post variable which allows the data in the form to persist after preview is clicked.

{{#tag:html|<form class="blog" method="POST">
<b>Title: </b><input name="newtitle" size="50" value="{{REQUEST:newtitle}}" />
<b>Summary</b>
<textarea name="summary" rows="3">{{REQUEST:summary}}</textarea>
<b>Content</b>
<textarea name="content" rows="10">{{REQUEST:content}}</textarea>
<b>Tags</b>
{{#ask:[[Category:Tags]]|format=template|template=Checkbox|link=none}}
<input type="submit" name="type" value="Post" />
<input type="submit" name="type" value="Preview" />
<input type="submit" name="type" value="News preview" />
<input type="hidden" name="action" value="blog" />
</form>}}
       
Blog post form.jpg


And here's the content of the Checkbox template which is called by the #ask query in the form to render a checkbox for every member of the Tags category. It first defines an name for the checkbox input that has any spaces replaced by underscores and prepends "tag" just in case it conflicts with an existing item such as "title", then it builds an input element using the name and adds a checked attribute if the name exists in the post data (i.e. if the item is being previewed.

{{#vardefine:name|tag{{#replace:{{{1}}}| |_}}}}
{{#tag:html|<div class="news-tags-input"><input type="checkbox" name="{{#var:name}}" {{#if:{{REQUEST:{{#var:id}}}}|checked}} /> {{{1}}}</div>}}

Processing the form

In form above a hidde value called action has been added which is set to the value "blog", so to create a new blog item from the posted form some PHP needs to be added to the unknown action hook. The main part of the code will be something like the following which creates some article content from the summary and content fields of the form within a Blog template and then adds all the tag categories (as well as a handy Blog item and "Posts by USERNAME" category too). These are wrapped in noinclude tags so that any query pages that embed blog items don't get categorised into all the tag categories.

^tag(.+)$

This code is incomplete as it only shows the actual article creation part. A complete version needs to attach to the UnknownAction hook, and needs to allow the preview buttons to work, and to raise a warning if the title is invalid or already exists first. There are two preview buttons so that users can test what it will look like when visiting the page proper or when its rendered within another page in a query. The Blog template renders both cases differently so that the full page has all the content as well as comments, but the "blog rolls" show only the summary and wrap it in a table.

A complete version of the code can be found here which can be added to LocalSettings.php and will add to the UnknownAction hook, raise the necessary warnings and redirect the user to the created post article, or display the form again if it was being previewed or an error occurred.

The Blog template

Blog-item articles are created with all the content passed within the Template:Blog which adds the author, tags and post date above the item, and adds a section below for users to add comments (here we use the DISQUS widgit, but the Extension:AjaxComments could also be used). Note that the template returns just the plain summary of the blog-item if the current page is not the actual post item so that queries on posts can decide on their own formatting and don't have all the comments and main content shown for every post in the list.

Template:Blog determines whether the current page is the actual news item rather than a page querying news items by checking if the current article is a member of Category:Blog items since only blog items are automatically categorised into this category by the post-form processing code shown above.

Here's the content of the template, see Template:Blog for the actual wikitext as this has white-space added and has the noinclude section and includeonly tags removed for clarity.

{{#vardefine:item|{{#show:{{FULLPAGENAME}}|?Category:Blog items}}}}
{{#if:{{#var:item}}|
  <div class="blog-sig">
    Posted by [[User:{{REVISIONUSER}}|{{REVISIONUSER}}]]
    on {{#time:d F Y \a\t H:i|{{#show:{{FULLPAGENAME}}|?Creation date}}}}
  </div>
  {{TagList|{{FULLPAGENAME}}}}
}}

{{{1}}}

{{#if:{{#var:item}}|
  {{{2}}}
  {{#widget:DISQUS|id=childrenarewelcome|uniqid={{FULLPAGENAME}}|url={{fullurl:{{FULLPAGENAME}}}}}}
}}

The first line defines a variable called item which is the result of an SMW #show query that test whether the current title is in the Blog items category. At the highest level the rest of the template is three sections, the first and the third are wrapped in conditions so that they only occur if item is true (i.e. the current title is an actual blog item not a query of a list of blog items. The second section is just {{{1}}} which is the summary text. So this means that queries of many blog items will be only the naked summary text and nothing else, where as actual blog item articles will also have the first section preceding the summary text, and the third section following it.

The first section can be broken down into two parts. The first the text that sites below the page title saying who posted the item and at what time and date. The use is simply obtained from the built-in REVISIONUSER magic word, and the date is obtained using the SMW #show query on the special property Creation date and then reformatted with #time provided by the ParserFunctions extension. The second part is a list of all tags the blog item belongs to which uses Template:TagList which we'll look at in detail below.

The third section is two parts, first just {{{2}}} which displays the main text content of the post, and then the next displays a section below the content for user feedback, in this case using the DISQUS widget.