Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #04b
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

a {color:[[ColorPalette::PrimaryMid]];}
a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
a img {border:0;}

h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}

.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}

.header {background:[[ColorPalette::PrimaryMid]];}
.headerShadow {color:[[ColorPalette::Foreground]];}
.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
.headerForeground {color:[[ColorPalette::Background]];}
.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}

.tabSelected{color:[[ColorPalette::PrimaryDark]];
	background:[[ColorPalette::TertiaryPale]];
	border-left:1px solid [[ColorPalette::TertiaryLight]];
	border-top:1px solid [[ColorPalette::TertiaryLight]];
	border-right:1px solid [[ColorPalette::TertiaryLight]];
}
.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
.tabContents .button {border:0;}

#sidebar {}
#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}

.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
	border:1px solid [[ColorPalette::PrimaryMid]];}
.wizardStep.wizardStepDone {background::[[ColorPalette::TertiaryLight]];}
.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}

#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}

.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}

.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}

.tiddler .defaultCommand {font-weight:bold;}

.shadow .title {color:[[ColorPalette::TertiaryDark]];}

.title {color:[[ColorPalette::SecondaryDark]];}
.subtitle {color:[[ColorPalette::TertiaryDark]];}

.toolbar {color:[[ColorPalette::PrimaryMid]];}
.toolbar a {color:[[ColorPalette::TertiaryLight]];}
.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}

.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button {border:none;}

.footer {color:[[ColorPalette::TertiaryLight]];}
.selected .footer {color:[[ColorPalette::TertiaryMid]];}

.sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
.sparktick {background:[[ColorPalette::PrimaryDark]];}

.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
.lowlight {background:[[ColorPalette::TertiaryLight]];}

.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}

.imageLink, #displayArea .imageLink {background:transparent;}

.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}

.viewer .listTitle {list-style-type:none; margin-left:-2em;}
.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}

.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}

.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
.viewer code {color:[[ColorPalette::SecondaryDark]];}
.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}

.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}

.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
.editorFooter {color:[[ColorPalette::TertiaryMid]];}

#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
/*}}}*/
/*{{{*/
* html .tiddler {height:1%;}

body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}

h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:1.35em;}
h2 {font-size:1.25em;}
h3 {font-size:1.1em;}
h4 {font-size:1em;}
h5 {font-size:.9em;}

hr {height:1px;}

a {text-decoration:none;}

dt {font-weight:bold;}

ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}

.txtOptionInput {width:11em;}

#contentWrapper .chkOptionInput {border:0;}

.externalLink {text-decoration:underline;}

.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}

.tiddlyLinkExisting {font-weight:bold;}
.tiddlyLinkNonExisting {font-style:italic;}

/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
a.tiddlyLinkNonExisting.shadow {font-weight:bold;}

#mainMenu .tiddlyLinkExisting,
	#mainMenu .tiddlyLinkNonExisting,
	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}

.header {position:relative;}
.header a:hover {background:transparent;}
.headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}

.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}

#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}

#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions {padding-top:0.3em;}
#sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}

.wizard {padding:0.1em 1em 0em 2em;}
.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizardStep {padding:1em 1em 1em 1em;}
.wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
.wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
.wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}

#messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
#messageArea a {text-decoration:underline;}

.tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
.popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}

.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
.popup .popupMessage {padding:0.4em;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
.popup li.disabled {padding:0.4em;}
.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}

.tabset {padding:1em 0em 0em 0.5em;}
.tab {margin:0em 0em 0em 0.25em; padding:2px;}
.tabContents {padding:0.5em;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}
.tabContents li.listLink { margin-left:.75em;}

#contentWrapper {display:block;}
#splashScreen {display:none;}

#displayArea {margin:1em 17em 0em 14em;}

.toolbar {text-align:right; font-size:.9em;}

.tiddler {padding:1em 1em 0em 1em;}

.missing .viewer,.missing .title {font-style:italic;}

.title {font-size:1.6em; font-weight:bold;}

.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}

.tiddler .button {padding:0.2em 0.4em;}

.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging, .tagged {font-size:0.9em; padding:0.25em;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagClear {clear:both;}

.footer {font-size:.9em;}
.footer li {display:inline;}

.annotation {padding:0.5em; margin:0.5em;}

* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer {line-height:1.4em; padding-top:0.5em;}
.viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}

.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}

.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}

.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}
.editorFooter {padding:0.25em 0em; font-size:.9em;}
.editorFooter .button {padding-top:0px; padding-bottom:0px;}

.fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}

.sparkline {line-height:1em;}
.sparktick {outline:0;}

.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
.zoomer div {padding:1em;}

* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
#backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/
/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which use a logographic writing system and need larger font sizes.
***/

/*{{{*/
body {font-size:0.8em;}

#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}

.subtitle {font-size:0.8em;}

.viewer table.listView {font-size:0.95em;}

.htmlarea .toolbarHA table {border:1px solid ButtonFace; margin:0em 0em;}
/*}}}*/
/*{{{*/
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton {display: none ! important;}
#displayArea {margin: 1em 1em 0em 1em;}
/* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
noscript {display:none;}
}
/*}}}*/
<!--{{{-->
<div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
<div class='headerShadow'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
</div>
<div id='mainMenu' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
<!--{{{-->
<div class='toolbar' macro='toolbar closeTiddler closeOthers +editTiddler > fields syncing permalink references jump'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<!--}}}-->
<!--{{{-->
<div class='toolbar' macro='toolbar +saveTiddler -cancelTiddler deleteTiddler'></div>
<div class='title' macro='view title'></div>
<div class='editor' macro='edit title'></div>
<div macro='annotations'></div>
<div class='editor' macro='edit text'></div>
<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser'></span></div>
<!--}}}-->
To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
* SiteTitle & SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
* MainMenu: The menu (usually on the left)
* DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
You'll also need to enter your username for signing your edits: <<option txtUserName>>
These InterfaceOptions for customising TiddlyWiki are saved in your browser

Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)

<<option txtUserName>>
<<option chkSaveBackups>> SaveBackups
<<option chkAutoSave>> AutoSave
<<option chkRegExpSearch>> RegExpSearch
<<option chkCaseSensitiveSearch>> CaseSensitiveSearch
<<option chkAnimate>> EnableAnimations

----
Also see AdvancedOptions
Asides, or short posts, are a common feature in blogs.  These are generally a few lines, and intended to be outside the loop of larger posts.  It's quite common it show these in a sidebar or big footer.

To accomplish this with Habari, you will employ the use of tags.  Your first order is to determine what tag you want to use.  In this example "aside" will be used.

The first file you will be editing will be your theme.php file.

You will be adding a few lines to your custom Class. (In k2 this class is called myTheme, your custom theme should be named something different)

First, we are going to exclude the aside tag from the main loop.
<pre>public function act_display_home()
{
    parent::act_display_home( array( 'not:tag' => 'aside' ) );
}
</pre>

(Note, if you use a different tag other than "aside", you will need to change it in all the code examples).

Next you are going to define a variable so that you can control how many posts to show, and output the list of posts.
<pre>$this->assign( 'asides', Posts::get( array( 'tag'=>'aside', 'limit'=>5) ) );
parent::add_template_vars();
</pre>
You can obviously change the limit variable to alter the number of items you are going to output.

Now that the theme.php file has been edited, the "aside" tag is excluded from the main loop of posts, and you are ready to output your list of asides.  Determine where you want to output this list (as mentioned, a sidebar is a very common place).

<pre><ul>
<?php
foreach($asides as $post):
echo '<li><span class="date">';
echo $post->pubdate_out . ' - ' . '</span>';
echo '<a href="' . $post->permalink .'"/>' . $post->title_out . '</a> - ';
echo '</li>'; ?>
<?php endforeach; ?>
</ul>
</pre>

In this example, Only the post title and a permalink to the post is shown.  You can add <code>echo $post->content_out;</code> where you want it shown in the foreach (ie, before the closing <code></li></code>)

You may also want to read over [[Using Excerpts]] if you want to have full length posts in your main content, and excerpts for your asides.

Here is [http://www.chrisjdavis.org/asides-with-habari another tutorial] for doing inline asides.  That is, asides listed along with primary posts, however with the ability to format them differently.
#REDIRECT [[Creating a Custom Theme]]
== Editing the Code ==
First, you must [[Installation#Downloading_Habari|download the source code]] from SVN.

Then, following the [[Coding Standards]], make your changes to the code.

Next, create a .diff file of the changes you have made. Here is an example of how to do this, where ''filename.php'' is the name of the file you've changed, and ''rXXX'' is the version you've made the change to.
<pre> svn diff filename.php > filename.php.rXXX.diff </pre>

Then, attach your .diff file to the appropriate issue in the [http://trac.habariproject.org/habari issue tracker].

Other users will then be able to apply your patch to test its compatibility with other configurations, and once tested and approved a comitter will then be able to apply your patch to the code to become a part of '''Habari'''
These are the Coding Standards for Habari:

*Use tabs instead of spaces for indentation
*for functions and classes, the opening brace { should be on its own line
*conditionals have the opening brace on the same line: <code>if ( foo == bar ) {</code>
*on an else condition, closing brace on new line, else and opening brace on same line: <code>} ''[line break]'' else {</code>
*Classes are named like so: <code>ClassName</code>
*Variables are named with underscores: <code>variable_name</code>
*spaces inside parentheses and operators: ( parentheses ), <code>foo == bar</code>
*no space before assignment, space after: <code>$foo= bar</code>

{{developer}}

[[Category:Manual]]
Habari's functionality can be modified or extended using plugins, which are written in PHP. Writing a plugin for Habari is extremely simple.

The steps below describe in detail how to create a plugin for Habari, and provide some examples of the sorts of things that can be done with a plugin.

== Step 1: Create The Plugin Class File ==

Create a new directory in your <tt>/user/plugins</tt> directory that uses the name of your plugin.  As an example, you could create: <tt>/user/plugins/mysample</tt>

In that directory, create a php file that contains the name of your plugin (the same as the directory) plus <tt>.plugin.php</tt> added to the end.  For our example, we would create:  <tt>/user/plugins/mysample/mysample.plugin.php</tt>

== Step 2: Create the Base Plugin Class == 

All plugins in Habari must extend the core <tt>Plugin</tt> class.  This allows the system to easily find and register your plugin. The following code creates a plugin called <tt>MyPlugin</tt>.

<code>
 class MyPlugin extends Plugin
 {
   function info()
   {
     return array(
       'name' => 'My Plugin',
       'version' => '1.0',
       'url' => 'http://habariproject.org/',
       'author' => 'Habari Community',
       'authorurl' => 'http://habariproject.org/',
       'license' => 'Apache License 2.0',
       'description' => 'Description',
     );
   }
 }
</code>

The <tt>info()</tt> member of the plugin is _required_.  It indicates the basic plugin information.  If you want your plugin to appear properly in Habari's list of plugins in the admin, then you need to provide these fields at a minimum:

;name: The name of the plugin as it will appear in the admin console.
;version: The version of your plugin.  It is important that this version number be compatible with PHP's [http://php.net/version_compare version_compare()] function if you want your plugin to be compatible with the habariproject.org update beacon.
;url: The URL location of your plugin.  This will be used to create a link to your plugin from the admin console.
;author: The name of the author of your plugin.  (Your name!)
;authorurl: The URL location of your blog/web site if any.
;license: The distribution license of your plugin.
;description: Description of your plugin.

You should not create an instance of your plugin (<tt>$foo = new MyPlugin()</tt>) within your plugin class file.  Habari will find your plugin and create an instance of it for you when your plugin is activated.  It is able to do this because your plugin extends the core <tt>Plugin</tt> class.  (If your plugin requires a separate class, Habari won't create an instance of it, because it wouldn't extend <tt>Plugin</tt>.)

== Step 3: Add Actions and Filters ==

The plugin system in Habari is based on Actions and Filters.

An Action occurs when a certain event in the software takes place.  For example, when a post is saved or displayed an Action is triggered.  An Action notifies your plugin that the event has taken place, allowing your plugin to perform some function.  Values that are passed to an action cannot be changed.

A Filter is similar to an Action but allows your plugin to change data that will be used by the system when an event occurs.  Filters are passed the value of a parameter to filter, and return that parameter, possibly having altered it in some way.  Some Filters also pass additional parameters to give plugins extra information.  An example is a Filter that responds to a comment submission and returns the "spamminess" of the comment.  The value that is returned from a Filter is passed to subsequent Filters as the first parameter, and is eventually used as the value for the filtered item.

Each Action or Filter has a name called a "hook name" that represents an event.  A plugin "hook" is the place in the code where Habari checks to see if any plugins need to be informed of an event.  A hook is either an Action or a Filter.  See [[Plugin Hooks]] for more information on plugin hooks, including a list of available Actions and Filters.  A plugin "sink" is one of the actual functions that is executed when the plugin hook code is run. Creating sinks that are activated for specific hooks is as simple as adding a method to your class with the appropriate name, either <tt>filter_hook_name</tt> or <tt>action_hook_name</tt>.

=== Action Example ===

Assume that you want your plugin to perform a task whenever the system has finished loading all plugins.  The hook name of this hook is <tt>plugins_loaded</tt>.  <tt>plugins_loaded</tt> is an Action.

To sink this action, create a new method in your plugin like so:

<code>
 function action_plugins_loaded()
 {
   Utils::debug('Hello!');
 }
</code>

The prefix of <tt>action_</tt> in your function name tells Habari that you are creating a sink for an Action.  The <tt>plugins_loaded</tt> part of the function name tells Habari what hook you want to sink.

When you activate the plugin with this function in it, the "Hello!" message will be displayed inside the pink debug box immediately after all active plugins are loaded.

Some Actions will pass values to the sink methods.  These values can be checked and acted upon.

=== Filter Example ===

Assume that you want your plugin to check the spamminess of a comment.  The hook name of a hook that will do this is <tt>spam_filter</tt>.  <tt>spam_filter</tt> is a Filter.

To sink this Filter, create a new method in your plugin like so:

<code>
 function filter_spam_filter($rating, $comment, $handler_vars)
 {
   if ( strpos( $comment->content, 'viagra' ) ) {
     $rating++;
   }
   return $rating;
 }
</code>

The prefix of <tt>filter_</tt> in your function name tells Habari that you are creating a sink for a Filter.  The <tt>spam_filter</tt> part of the function name tells Habari what hook you want to sink.

When you activate the plugin with this method, Habari will pass every submitted comment through it.  The function looks for the word "viagra" in the content of the comment, and upon finding it, increases the spam rating of the comment.  

Finally, the rating is returned for use by the next plugin, and by the system.  Note that even when no adjustment is made to the value of <tt>$rating</tt> it *must* be returned, otherwise the system will not have a value to process.

In this sink method, the <tt>$handler_vars</tt> parameter is provided to the method by the system, but it is not used by the method.  This parameter could have been omitted in the method declaration, but it might be useful to leave it there for future use.

=== XMLRPC Example ===

Implementing XMLRPC via plugins is very easy by use of a special prefix for a plugin function.

<code>
 function xmlrpc_namespace__function($param1, $param2, ...);
</code>

A hook implemented in this way uses the "namespace" part of the hook name as the XMLRPC namespace, and the "function" part as the XMLRPC function.  The two are separated by two underscore characters.

Values passed from to this hook function are those interpreted by processing the incoming values posted to Habari's XMLRPC endpoint.

To return a properly formatted XMLRPC value, simply return a standard PHP data type.  This will be converted into a proper XMLRPC response by the plugin system.

== Step 4: Common Plugin Tasks ==

The previous steps provide instructions for constructing the basic structure of a Habari plugin.  With that knowledge, leveraging the many plugin hooks is a simple task.

What follows are a few things that are common tasks when creating a plugin.

=== Add Output to a Theme ===

So, you have created your killer plugin and want to provide theme developers a chance to show off your work.  Based on the action example above, you can do something like this:

<code>
 function action_add_template_vars( $theme ) {
 {
   $theme->yourvariable= 'Sweet Plugin Output';
 }
</code>

This makes <tt>$yourvariable</tt> available in the theme, where you can do something like 

<code>
<?php echo $yourvariable ?>
</code>

and that will output the value directly in the theme template file.

=== Use FormUI to Create an Options Page ===

Adding an options control to your plugin is also very easy, and most users will be more comfortable with setting the options via the admin panel than editing plugin code directly. Thankfully Habari provides a mechanism called FormUI that enables plugin developers to quickly create their own option controls.

The basic structure that needs to be added to the plugin, is as follows:
<code>
        public function filter_plugin_config( $actions, $plugin_id )
        {
                if ( $plugin_id == $this->plugin_id() ) {
                        $actions[]= _t('Configure');
                }
                return $actions;
        }
</code>
The _t() function translates the string into the language used by the site.  For English-language blogs, the output would be "Configure", while for Spanish-language blogs, for example, the output might be "Configurar".
<code>
        public function action_plugin_ui( $plugin_id, $action )
        {
                if ( $plugin_id == $this->plugin_id() ) {
                        switch ( $action ) {
                                case _t('Configure') :
                                        $ui = new FormUI( strtolower( get_class( $this ) ) );
                                        $customvalue= $ui->add( 'text', 'customvalue', _t('Your custom value:') );
                                        $ui->on_success( array( $this, 'updated_config' ) );
                                        $ui->out();
                                break;
                        }
                }
        }
</code>
<code>
        public function updated_config( $ui )
        {
                return true;
        }

</code>

The first function, <tt>filter_plugin_config</tt>, creates a simple configure link for the plugin, on the Plugin Administration page.
<tt>action_plugin_ui</tt> adds the form controls, in this example a text field called <tt>customvalue</tt> is added.
The call to <tt>on_success</tt> registers a callback function, in this case <tt>updated_config</tt>. This is called when the FormUI is successfully submitted to allow you to perform actions if the form is successfully submitted and passes validation.  If the FormUI object makes use of the on_success callback, it must return true from the callback for FormUI to save the options to the options table.   If the on_success callback returns false, then the callback is responsible for saving its own options.  If no callback is defined, then the fields will be saved automatically.  When FormUI saves fieldvalues, the fields are saved in the Options table in the format <tt>{plugin_name}:{field_name}</tt>, your plugin name in lowercase and the field name separated by a colon.

To read the value of the variable have defined in an Action or Filter, call <tt>Options::get( 'myplugin:customvalue' );</tt>, and build logic around the value it returns.

To store a user-specific option - an option that is different for each logged-in user - apply the prefix "user:" to the name of the added control.  Instead of being stored in the options table, the user-specific option will be stored in the user's info record as <tt>{plugin_name}_{field_name}</tt>.  So a plugin named "Foo" with an option named "user:bar" will have its value accessible from:
<code>User::->identify()->info->foo_bar</code>

For more information about using the FormUI, including other controls and validation, see [[Building Forms]].

=== Change the Priority of Execution for Your Plugin ===

Sometimes you want your implementation of a plugin hook to execute before certain core hooks or other plugins' implementations.  To do this, you need to set the priority of your hooks.

By default, the priority of every hook in your plugin is 8.  Hooks with a lower priority execute sooner.

To set the priority of one of your hooks, add the method <tt>set_priorities()</tt> in your plugin class.  Have it return an associative array using the full function name of the hook (including the 'filter_' or 'action_' prefix) as the key and the priority integer as the value.

<code>
 function set_priorities()
 {
   return array(
     'action_add_template_vars' => 10,
   );
 }
</code>

Habari will process this function for priorities, and use them to order the execution of plugin hooks.

=== Create an Automatic Update Notification ===

Habari can notify users of changes to your plugin if you follow a few simple steps.

'''Step 1:'''
Get GUID, for example from http://www.somacon.com/p113.php, using the "Normal" setting.

'''Step 2:'''
Add this function to your plugin, and replace the strings therein:

<code>
 function action_update_check() {
 	Update::add( 'My Plugin Name', 'My GUID (from link above)', $this->info->version ); 
 }
</code>

'''Step 3:'''
Beacon settings for plugins are stored on http://habariproject.org. You should update these when your plugin is updated. There will eventually be a script that processes changes to the beacon information, but this script does not yet exist.  To update the beacon, contact someone with access to edit files on the server. Be sure to specify a brief description of changes and whether the update is a "Critical", "Feature", or "Bugfix" update.

{{developer}}

[[Category:Manual]]
Writing a plugin in Habari is dirt-simple.

First, have in mind the idea for your plugin, and what you might like to call it, then proceed with the steps below.

== Step 1: Create The Plugin Class File ==

Create a new directory in your <tt>/user/plugins</tt> directory that uses the name of your plugin.  As an example, you could create: <tt>/user/plugins/mysample</tt>

In that directory, create a php file that contains the name of your plugin (the same as the directory) plus <tt>.plugin.php</tt> added to the end.  For our example, we would create:  <tt>/user/plugins/mysample/mysample.plugin.php</tt>

== Step 2: Create the Base Plugin Class == 

All plugins in Habari should be created as a class.  This allows the system to easily find and register your plugin.  The class you create must extend the core <tt>Plugin</tt> class:

<pre>class MyPlugin extends Plugin
 {
   function info()
   {
     return array(
       'url'=>'http://habariproject.org',
       'name'=>'My Plugin',
       'license'=>'Apache License 2.0',
       'author'=>'Owen Winkler',
       'version'=>'1.0',
     );
   }
 }</pre>

The <tt>info()</tt> member of the plugin is _required_.  It indicates the basic plugin information.  If you want your plugin to appear properly in Habari's list of plugins in the admin, then you need to provide these fields at a minimum:

;name: The name of the plugin as it will appear in the admin console.
;url: The URL location of your plugin.  This will be used to create a link to your plugin from the admin console.
;license: The distribution license of your plugin.
;author: The name of the author of your plugin.  (Your name!)
;version: The version of your plugin.  It is important that this version number be compatible with PHP's [http://php.net/version_compare version_compare()] function if you want your plugin to be compatible with the habariproject.org update beacon.

You should not create an instance of your plugin (<tt>$foo = new MyPlugin()</tt>) within your plugin class file.  Habari will find your plugin and create an instance of it for you when your plugin is activated.  It is able to do this because your plugin extends the core <tt>Plugin</tt> class.  (If your plugin requires a separate class, Habari won't create an instance of it, because it wouldn't extend <tt>Plugin</tt>.)

== Step 3: Add Actions and Filters ==

The plugin system in Habari works based on Actions and Filters.

An Action occurs when a certain event in the software takes place.  This could happen when a post is saved or displayed.  An Action does not allow you to change the values that are passed to it.  It simply notifies you that the event has taken place.

A Filter is processed in the software when you should be allowed to change data that will be used by the system.  This could happen when a comment is submitted and the "spamminess" of the comment needs to be returned.  The value that is returned from a filter is passed to subsequent filters as the first parameter, and eventually used as the value for the filtered item.

Each action or filter has a name called a "hook name".  A plugin "hook" is the place in the code where plugin code is executed.  A plugin "sink" is one of the actual functions that gets executed when the plugin hook code is run.  The methods in your plugin class are "sinks".

Creating sinks that are activated for specific hooks is as simple as adding a method to your class with the appropriate name.

=== Action Example ===

Assume that you want your plugin to perform a task whenever the system has finished loading all plugins.  The hook name of this hook is <tt>plugins_loaded</tt>.  <tt>plugins_loaded</tt> is an action.

To sink this action, create a new method in your plugin like so:

<pre>function action_plugins_loaded()
 {
   Utils::debug('Hello!');
 }</pre>

The prefix of <tt>action_</tt> in your function name tells Habari that you are creating a sink for an action.  The <tt>plugins_loaded</tt> part of the function name tells Habari what hook you want to sink.

When you activate the plugin with this function in it, you should see the "Hello!" message from inside the pink debug box.  This is executed immediately after all active plugins are loaded.

Some actions will pass values to the sink methods.  These values can be checked and acted upon.

=== Filter Example ===

Assume that you want your plugin to check on the spamminess of a comment.  The hook name of a hook that will do this is <tt>spam_filter</tt>.  <tt>spam_filter</tt> is a filter.

To sink this filter, create a new method in your plugin like so:

<pre>function filter_spam_filter($rating, $comment, $handler_vars)
 {
   if( strpos( $comment->content, 'viagra' ) ) {
     $rating++;
   }
   return $rating;
 }</pre>

The prefix of <tt>filter_</tt> in your function name tells Habari that you are creating a sink for a filter.  The <tt>spam_filter</tt> part of the function name tells Habari what hook you want to sink.

When you activate the plugin with this method, Habari will pass every submitted comment through it.  The function looks for the word "viagra" in the content of the comment, and upon finding it, increases the spam rating of the comment.  

Finally, the rating is returned for use by the next plugin, and by the system.  Note that even when no adjustment is made to the value of <tt>$rating</tt>, <tt>$rating</tt> *must* be returned, otherwise the system will not have a value to process.

In this sink method, the <tt>$handler_vars</tt> parameter is provided to the method by the system, but it is not used by the method.  This parameter could have been omitted in the method declaration, but it might be useful to leave it there for future use.

=== XMLRPC Example ===

Implementing XMLRPC via plugins is very easy by use of a special prefix for a plugin function.

<pre>function xmlrpc_namespace__function($param1, $param2, ...);</pre>

A hook implemented in this way uses the "namespace" part of the hook name as the XMLRPC namespace, and the "function" part as the XMLRPC function.  The two are separated by two underscore characters.

Values passed from to this hook function are those interpreted by processing the incoming values posted to Habari's XMLRPC endpoint.

To return a properly formatted XMLRPC value, simply return a standard PHP data type.  This will be converted into a proper XMLRPC response by the plugin system.

== Step 4: Common Plugin Tasks ==

The previous three steps provide instructions for constructing the basic structure of a Habari plugin.  With that knowledge, leveraging the many plugin hooks is a simple task.

What follows are a few things that are common tasks when creating a plugin.

=== Add Output to a Theme ===

So, you have created your killer plugin and want to provide theme developers a chance to show off your work.  Based on the action example above, you can do something like this:

<pre>function action_add_template_vars( $theme ) {
 {
   $theme->yourvariable= 'Sweet Plugin Output';
 }</pre>

This makes <tt>$yourvariable</tt> available in the theme, where you can do something like 

<pre><?php echo $yourvariable ?></pre>

and that will output the value directly in the theme template file.


=== Use FormUI to Create an Options Page ===

Adding an options control to your plugin is also very easy, and most users will be more comfortable with setting the options via the admin panel than editing the plugins code directly. Thankfully Habari provides a mechanism called FormUI that enables plugin developers to quickly create their own option controls.

The basic structure that needs to be added to the plugin, is as follows:
<pre>public function filter_plugin_config( $actions, $plugin_id )
        {
                if ( $plugin_id == $this->plugin_id() ) {
                        $actions[] = _t('Configure');
                }
                return $actions;
        }</pre>
The _t() function translates the string into the language used by the site.  For English-language blogs, the output would be "Configure", while for Spanish-language blogs, for example, the output might be "Configurar".
The _e() function echoes a translated string for display.
<pre>public function action_plugin_ui( $plugin_id, $action )
        {
                if ( $plugin_id == $this->plugin_id() ) {
                        switch ( $action ) {
                                case _t('Configure') :
                                        $ui = new FormUI( strtolower( get_class( $this ) ) );
                                        $customvalue= $ui->add( 'text', 'customvalue', _t('Your custom value:') );
                                        $ui->on_success( array( $this, 'updated_config' ) );
                                        $ui->out();
                                break;
                        }
                }
        }</pre>
<pre>public function updated_config( $ui )
        {
                return true;
        }</pre>

The first function <tt>filter_plugin_config</tt> enables a simple configure link for the plugin, on the Plugin Administration page.
<tt>action_plugin_ui</tt> adds the form controls, in this example a text field called <tt>customvalue</tt> is added.
<tt>updated_config</tt> returns true if the data in the field has been updated.

Now, if you want to read the value of the variable have defined, all you have to do is call <tt>Options::get( 'myplugin:customvalue' );</tt> and build your logic around the value it returns.

=== Change the Priority of Execution for Your Plugin ===

Sometimes you want your implementation of a plugin hook to execute before certain core hooks or other plugins' implementations.  To do this, you need to set the priority of your hooks.

By default, the priority of every hook in your plugin is 8.  Hooks with a lower priority execute sooner.

To set the priority of one of your hooks, add the method <tt>set_priorities()</tt> in your plugin class.  Have it return an associative array using the full function name of the hook (including the 'filter_' or 'action_' prefix) as the key and the priority integer as the value.

<pre>function set_priorities()
 {
   return array(
     'action_add_template_vars' => 10,
   );
 }</pre>

Habari will process this function for priorities, and use them to order the execution of plugin hooks.
In addition to the ability of using custom templates to display output for particular requests, Habari allows customization of the code that controls which templates are called for display and what values are passed to them.

To accomplish this, Habari attempts to load a <tt>theme.php</tt> file located in the theme directory.  If this file is present, it is included in the code before the theme is rendered for any incoming request.

To override the core behavior for themes (defined in <tt>/system/classes/theme.php</tt>), define a new class structure in the theme's <tt>theme.php</tt> that extends the core <tt>Theme</tt> class.  

Consider this custom class:

<source lang="php">
// Set a custom theme to use for all public page (UserThemeHandler) theme output
define('THEME_CLASS', 'CustomTheme');

/**
 * A custom theme class
 * Custom themes are not required for themes, but they are handy in letting you 
 * define your own output data and possibly even additional, non-standard templates.
 **/   
class CustomTheme extends Theme
{

	/**
	 * Override Theme::display_posts() to provide alternate post display behavior
	 **/	 	 	
	public function act_display_posts()
	{
		parent::act_display_posts();
	}

	/**
	 * Add Theme::act_display_mine() to provide new, custom display behavior
	 **/	 	 	
	public function act_display_mine()
	{
		parent::act_display_posts();
	}

}
</source>

This class defines two custom functions for output.  Both of these functions would be executed based on the action field value set for the <tt>rewrite_rules</tt> record that matches an incoming URL request.

[[Category:Asides]]
[[Category:Theming]]
[[Introduction]]

Many clients exist to allow you to edit your blog and post new content from your desktop without having to interact with the admin interface.

Habari supports Atom Publishing Protocol by default, and with a MetaWeblog plugin, most desktop blogging clients should be able to post to Habari.  Consult the following chart to determine which clients will work best.

{| class="wikitable" border="1" cellspacing="0" cellpadding="5"
|-
! Client Name
! OS
! Protocol Support
! Create Posts
! Edit Posts
! Delete Posts
|-
| [http://www.red-sweater.com/marsedit/ MarsEdit]
| OSX
| MetaWeblog
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
|-
| [http://www.scribefire.com/ ScribeFire]
| Firefox
| MetaWeblog
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
|-
| [http://infinite-sushi.com/software/ecto/ Ecto]
| OSX
| MetaWeblog
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
|-
| [http://infinite-sushi.com/software/ecto/ Ecto]
| Windows
| *
| *
| *
| *
|-
| [http://www.codingrobots.com/blogjet/ BlogJet]
| Windows
| MetaWeblog
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" | no
|-
| [http://www.anconia.com/rocketpost/demo/ RocketPost]
| Windows
| MetaWeblog
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" | no
|-
| [http://get.live.com/betas/writer_betas Windows Live Writer]
| Windows
| MetaWeblog
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
|-
| [http://www.qumana.com/ Qumana]
| OSX
| MetaWeblog
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" | no
|-
| [http://homepage.mac.com/dschimpf/ MacJournal]
| OSX
| Atom
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ffdddd" | no
|-
| [http://options.wordpress.com/2006/01/08/dotpost/ DotPost]
| Windows
| MetaWeblog
| style="background-color:#ddffdd" | yes
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" | no
|}
This page should be the main index to learning about '''Habari''' internals, plugin development, etc.

* [[QueryRecord]]: the building block of Habari
* [[Coding Standards]]: generally accepted practiced within the project
* [[Habari Workflow]] : how '''Habari''' processes requests
* [[Code Submisson]] : how to submit code and patches for '''Habari'''
* [[Multisite]] : how multi-site configurations work in '''Habari'''
* [[User Overrides]] : how to use the `/user/classes` directory to override core '''Habari''' functionality
* [[Creating_A_Plugin|Creating A Plugin]] : tutorial on Habari Plugin architecture
* [[Rewrite Tutorial]] : How to construct rewrite rules for '''Habari'''
* [[Stack Class]] The Stack class allows a developer to create a "stack" of items for later output.

Additionally, as development is still in a state of rapid growth, you can consult the online wiki to learn more about [http://wiki.habariproject.org/en/Habari_Classes classes] and [http://wiki.habariproject.org/en/Plugin_Hooks plugin hooks].
==What does "habari" mean?==
"Habari" is a Swahili word that means "what's the news?"  It's similar to the English "What's up?" or the Spanish "¿Qué pasa?"  The typical response is "mzuri sana", which means "very good."

==What are the system requirements?==
Habari is a modern solution for blogging, and requires modern software.  To successfully use Habari, you will need:

* A web server ([http://httpd.apache.org/ Apache], [http://www.lighttpd.net/ lighttpd] and [http://nginx.net/ nginx] tested so far)
* [http://www.php.net/ PHP] version 5.2.x or above.  Habari ''will not'' work with PHP 4.
* [http://www.php.net/pdo PHP Data Objects] support
* A database.  [http://www.mysql.com/ MySQL] and [http://www.sqlite.org/ SQLite] tested,  [http://www.postgresql.org/ PostgreSQL] coming soon.

Habari has been successfully tested on Etch, the stable version of [http://www.debian.org/ Debian].

==Will Habari work with my web host?==
Habari should work with any web host that supports the above requirements. A list of web hosts where Habari has been tested and run successfully can be found at [[Supported Hosts]].

==How is this different from the eleventy billion other blog packages?==
It's true that there are tons of blogging software solutions from which to choose.  Each has their place, and their legion of ardent followers.

Habari is being written with a firm understanding of the current state of blogging.  Most other blogging packages have been around long enough that their responses to things like comment spam and Digg site overloads are bolted on after the fact; whereas Habari is being written from the beginning to take these things -- and more -- into account.

Habari strongly favors open, standard, and documented protocols.  Atom, being both open and documented, is the prefered syndication format, and the Atom Publishing Protocol is the prefered means of remote communication with your site.  This is a core feature, and not a plugin.

Habari is being written specifically for modern web hosting environments, and uses modern object-oriented programming techniques.  Using these recent but well-established additions to the PHP language allows Habari to make use of PDO, enabling prepared statements for all interactions with the database.  This greatly reduces the system's vulnerability to SQL injection attacks.  This is just one of many benefits of modern object-oriented techniques.

Those are just a few of the technical differences, but a major component of what makes Habari different is its community participation model.  Users who demonstrate a consistent level of quality contributions to the project are granted more privileges within the project.

==Why not fork an existing project?==
None of the packages we've tried have really satisfied us, so in the fine tradition of open source software we're trying to scratch our own itch.  

To be sure, there's a lot that many blog packages do right, and we'd be foolish to re-invent the wheel simply to be different.  But many existing blog packages have made fundamental decisions that limit what can be done, or how, with the system.  Rather than try to work around those limitations, or try to remove what's broken, we'd prefer to start fresh and import those ideas that are good.

==How is Habari licensed?==
Habari uses a modified version of the Apache License (http://www.apache.org/licenses/LICENSE-2.0). For those unfamiliar with this license, the [http://www.apache.org/foundation/licence-FAQ.html Apache License FAQ page] should answer most of your questions.

Developers contributing to the Habari project itself should note that, unless explicitly stated otherwise, any contribution intentionally submitted for inclusion shall be under the terms and conditions of the Habari license, without any additional terms or conditions. However, plugins and themes designed to work with Habari are not required to have the same license as Habari itself.

You can find our license included in the source, and http://svn.habariproject.org/habari/trunk/LICENSE in our code repository].

==Why HTML vs XHTML==
A consensus amongst project members concluded a thorough discussion as to the merits of the two DOC types, and what would be appropriate for the Admin pages of Habari.  The conclusion was that HTML was the "proper" way, however, end users are welcome to serve their pages in any way they prefer by declaring the DOC type in their theme.  A more [[XHTML_vs_HTML|comprehensive outline]] of the discussion, culled from the information in the original discussion is provided.

==How can I help?==
* Open new issues [http://trac.habariproject.org/habari/ on our issue tracker] describing problems you experience, or to request new features.
* Join the Google Group(s) in which you have the most interest, and start sharing!
** The [http://groups.google.com/group/habari-users habari-users] group is for end user support, and general discussion.
** The [http://groups.google.com/group/habari-dev habari-dev] group is for nitty-gritty implementation discussions of the code, database schemas, and the like.
** The [http://groups.google.com/group/habari-svn habari-svn] group is a way to track the development activity of the source code.  You can review the commit logs, and monitor changes as they occur.
** The [http://groups.google.com/group/habari-issues habari-issues] group is a way to follow by email the Issue Tracker here at Google Code.

==How do I add my code changes to the project?==

If you would like to work on a feature or bug fix, there is a best practice for getting your changes into the process.

Regardless of the type of fix/change, first create an issue in the issue tracker describing your proposed change.  Allowing the group to review your proposal could save you the time of implementing something that is being done as part of a larger task, and could gain you the insight or help of other people who are interested in what you're doing.

As you write your code, review Habari's [[Coding Standards|coding standards]] to get acquainted to how your code should be formatted.
As you prepare your changes, ask for review on the development mailing list as often as you need it.

When your changes are complete, file code changes as a diff in the issue that you're tracking, and attach any associated files.  Be sure to mention how the files are to be applied if there are any special considerations.  Your issue will be reviewed, and if suitable, will be applied in the code repository.

[[Category:Manual]]
Like most wikis, TiddlyWiki supports a range of simplified character formatting:
| !To get | !Type this |h
| ''Bold'' | {{{''Bold''}}} |
| --Strikethrough-- | {{{--Strikethrough--}}} |
| __Underline__ | {{{__Underline__}}} (that's two underline characters) |
| //Italic// | {{{//Italic//}}} |
| Superscript: 2^^3^^=8 | {{{2^^3^^=8}}} |
| Subscript: a~~ij~~ = -a~~ji~~ | {{{a~~ij~~ = -a~~ji~~}}} |
| @@highlight@@ | {{{@@highlight@@}}} |
<<<
The highlight can also accept CSS syntax to directly style the text:
@@color:green;green coloured@@
@@background-color:#ff0000;color:#ffffff;red coloured@@
@@text-shadow:black 3px 3px 8px;font-size:18pt;display:block;margin:1em 1em 1em 1em;border:1px solid black;Access any CSS style@@
<<<

//For backwards compatibility, the following highlight syntax is also accepted://
{{{
@@bgcolor(#ff0000):color(#ffffff):red coloured@@
}}}
@@bgcolor(#ff0000):color(#ffffff):red coloured@@

''Workflow'' might be the wrong term.  This page will attempt to describe in general terms what happens when Habari receives a request from a user.  The process is basically the same for visitors (anonymous or otherwise) and users (administrators or otherwise), but each will be explained in detail for your benefit.

For the purposes of this document, we will assume that a client somewhere on the internet is accessing an instance of Habari at <code><nowiki>http://habariproject.org/en/</nowiki></code>.

==Set up==
The client computer will initiate an HTTP request to <code><nowiki>http://habariproject.org/en/</nowiki></code>.  The Habari instance on the server will accept the request. All requests begin in the same way. This section describes Habari's set up process.

===mod_rewrite===
Before Habari is ever invoked, the web server software (Apache, Lighttpd, IIS) will evaluate whether the incoming request is an actual file on the server's hard disk(s) or not.  If the request is for a specific file that does actually exist, the web server software will deliver this file to the client, and Habari will not be involved.  This happens when serving <code>.css</code> stylesheets, for example.  This might also happen when serving media files (images, podcasts, etc).

If the client request is not for a specific file that exists on the server's hard disk(s), the web server software will pass the request on to the Habari <code>index.php</code>.

===Version Check===
The very first thing <code>index.php</code> does is to determine whether the version of PHP installed on the server is at least version 5.2.0 or above.  This is the absolute bare minimum version of PHP that Habari will support.

===HABARI_PATH===
Next, <code>index.php</code> sets a constant -- <code>HABARI_PATH</code> -- that will be used throughout the program.  This constant contains the filesystem path to the <code>index.php</code> file.  For example, if Habari is installed at <code>/home/habari/public_html</code>, then <code>HABARI_PATH</code> will contain ''/home/habari/public_html''.  The <code>HABARI_PATH</code> constant is used in many places throughout the Habari code.

===Output Buffering===
Once the constant has been set, Habari enables output buffering.  This is a process to delay sending any data to the client until such time as the server is ready to do so.  In the normal execution of a web site, content is delivered to the client directly from the server as it is read in by the server.  With output buffering, content is held on the server until the script on the server specifically tells the server to send it to the client.

===__autoload()===
The next portion of the <code>index.php</code> defines a function, <code>__autoload()</code>, that represents one of the fundamental building blocks of Habari.  Although it is defined here in <code>index.php</code>, it is not executed at this time.  The <code>[http://www.php.net/autoload __autoload()]</code> function automatically locates and loads any PHP class files that are referenced for use within Habari.  This function looks for class files first in the Site classes directory, if it exists, and then in <code>HABARI_PATH/system/classes</code>.  By using the <code>__autoload()</code> function, Habari is able to dynamically load class files as they are needed, without requiring the Habari developers to pre-define every single class file and its location.

===Error Reporting===
With the <code>__autoload()</code> function defined, Habari now sets PHP error reporting to "All".  This step is primarily to aid server administrators by ensuring that sufficient diagnostic information is recorded in the web server error logs if something goes wrong.  Without this, it is often difficult to know exactly where a problem has occurred, or why.

===revert_magic_quotes_gpc()===
Next Habari invokes a function called <code>revert_magic_quotes_gpc()</code> inside the <code>Utils</code> class.  Because the <code>Utils</code> class is not yet defined, the <code>__autoload()</code> function is triggered.  <code>__autoload()</code> searches for a file named <code>utils.php</code> in one of several directories.  Unless you have created your own <code>utils.php</code> file, Habari will load <code>HABARI_PATH/system/classes/utils.php</code> and execute the <code>revert_magic_quotes_gpc()</code> function therein.  This function undoes "[http://www.php.net/magic_quotes magic quotes]".  Habari does not rely on magic quotes in the same way that many other tools do.

===config.php===
With all of that set, Habari is finally ready to load the <code>config.php</code> file that specifies how to connect to the database.  A single installation of the Habari source code can power many sites, so there are a number of places in which the <code>config.php</code> file might be found.  The <code>get_config_path()</code> function in the <code>Site</code> class determines where the correct <code>config.php</code> file is for this invocation of Habari.  Remember, the <code>Site</code> class will be automatically loaded by the <code>__autoload()</code> function!

If Habari is only being used for a single site -- say a personal blog -- then the <code>config.php</code> file should reside inside the <code>HABARI_PATH</code> directory.  However, Habari has no immediate way of determining whether it is being used for one site or one hundred sites.  <code>get_config_path()</code> does the following:
* defines the path to the <code>config.php</code> as <code>HABARI_PATH</code>, by default
* looks for the existence of any <code>config.php</code> files inside any sub-directories of <code>HABARI_PATH/user/sites</code>
** if none are found, return the default path of <code>HABARI_PATH</code>
* Compare the URL used to access Habari for this request with the directories in <code>HABARI_PATH/user/sites</code>, using the following formula:
** strip off the protocol used to access the site.  <code><nowiki>http://habariproject.org/en/</nowiki></code> becomes <code>//habariproject.org/en/</code>
** replace all slashes with periods.  So <code>habariproject.org/en</code> becomes <code>habariproject.org.en</code>.
** place any non-standard HTTP port at the beginning of the directory.  So <code>habariproject.org/en:8080</code> becomes <code>8080.habariproject.org.en</code>
** if a match exists between the URL used for this request and a directory, return that directory.  For example, <code><nowiki>http://habariproject.org/en/</nowiki></code> matches <code>HABARI_PATH/user/sites/habariproject.org.en</code>

===Install?===
If there is no <code>config.php</code>, or if it is incomplete, the Habari installation process will be invoked.  If there is a valid <code>config.php</code>, Habari will attempt to connect to the database.  If that fails, the installation process will be invoked.

===Content Type===
Habari next forcibly sets the content type to <code>text/html;charset=utf-8</code>.

===Locale===
Habari sets the locale, which governs the language used to display Habari-generated messages.  The default locale is U.S. English.

===Plugins===
Plugins are loaded next, and the very first plugin hook is triggered.  Any plugins registered against the <code>plugins_loaded</code> hook will be executed.  See [[Plugins]] for more information.

===Controller===
The controller ( '''C''' in MVC ([http://en.wikipedia.org/wiki/Model-view-controller model-view-controller]) "[p]rocesses and responds to events, typically user actions, and may invoke changes on the model."  The controller first examines the request (<code>Controller::parse_request();</code>).  This compares the requested URL against the list of registered actions in the <code>rewrite_rules</code> database table.  If a match is found, the action specified in the rule is invoked against the handler specified in the rule.  For more information on rewrite rules, see the [[Rewrite Tutorial]].  The controller contains an array of <code>handler_vars</code>, which are set by HTTP POST and GET values passed to this request, as well as any values set by plugins. Execution from this point is different, depending on which handler is invoked according to the rewrite rule matched. The following sections describe in the workflow for different handlers.

==End User Workflow==
In the event that the requested URL is simply to a public page of the blog, that is for the normal case of a user viewing the site, the handler will be UserThemeHandler.  UserThemeHandler evaluates the request (home page, single post view, date-based archive view, etc) and loads the proper theme template files.  For information on how Habari chooses which template file to use, see [[Template File Hierarchy]].  The handler will place the <code>handler_vars</code> into the theme's global variable space.  The template files are then delivered to the viewer.

==Administrative Workflow==
For requests to the administration section of Habari, those whose path begins with <code>/admin</code> the AdminHandler will be invoked.

===Authentication and Authorisation===
Only users with administration privileges can access pages under <code>/admin</code>, so the first thing that AdminHandler does is check if the user is logged in, and if not redirects them to the login page. The user's privileges are then checked, and if they do not have administrative access an error message is displayed.

===Routing the request===
Once the user is authenticated and authorised, control is passed to <code>AdminHandler->act_admin()</code> which decides where the request should be routed based on the HTTP method used (GET or POST) and the page requested (the portion of the URL following <code>/admin</code>) to the appropriate function in <code>AdminHandler</code>.  For example, a GET request to <code>/admin/publish</code> will be sent to <code>AdminHandler->get_publish()</code>, and the handler will prepare the publish page for display.

===Administration Output===
The administration section has its own theme, located in <code>HABARI_PATH/system/admin</code>. Each of the functions in <code>AdminHandler</code> specify which template should be used as their output, which is then output in the same way as any other theme.

==Flush Output==
The very last thing that <code>index.php</code> does is to flush the output buffer.  Any output that has been prepared is finally sent to the browser.

{{developer}}

[[Category:Manual]]
Need help [[Restoring a WordPress SQL file]]? This page is about importing directly from a WordPress database.

Habari currently ships with a plugin to import posts, comments, tags (including Ultimate Tag Warrior Tags) and users from WordPress (current version confirmed to work with 2.3.1, mileage will vary with older versions).  Other importers are planned.

You must first activate the plugin before navigating to the admin/import page.  After activating the plugin, you will get a select box to choose what type of content you are importing (as mentioned, WordPress is the only importer currently available).

One note about importing, due to the feature of importing users, you can not have the same user name in your Habari install at the time of import (this can easily be changed under admin/users/ ->edit, and change the name.  You can change back after importing.  Example, if your Habari install uses admin and your WordPress install uses admin, change your Habari name to admin2 for the import, and change it back after importing). '''For the upcoming .4 release, and as of revision 1319 this issue has been resolved.'''

You will then be prompted to fill in your WordPress database information, and choose your options for tag importation.

It is suggested you set up a separate database for your Habari install from that of your WordPress installation. The import process will be much faster and less room for error. WordPress pages should import as Habari pages, and everything else either an entry or draft.

Approved and unapproved comments should also be imported.

Note that WordPress permalinks and paths will be lost, and you should explore options with .htaccess if you are concerned with maintaining your previous links. Andrew da Silva's [http://www.habariproject.org/dist/plugins/route301.zip Route 301] plugin may help in some cases. Alternatively, you can set up a custom permalink scheme by adding a rewrite rule to your Habari database. Feel free to post to [http://groups.google.com/group/habari-users Habari users] if you have any questions or problems with your migration.

[[Category:Manual]]
==Before You Install==

=== Server Requirements ===
*Supported Web Server
**[http://httpd.apache.org/ Apache (1.3.x or higher, 2.x or higher recommended)] with [http://httpd.apache.org/docs/1.3/mod/mod_rewrite.html mod_rewrite] enabled
**[http://www.lighttpd.net/ Lighttpd]
**[http://www.nginx.net/ Nginx]
*Supported Database
**MySQL 4.1.x or greater
**SQLite
*PHP version 5.2 or above
**PHP Data Objects (PDO) enabled
**SimpleXML enabled

''Support for PostgreSQL is planned.''

To install habari on [http://dreamhost.com/ Dreamhost]you need to make sure that you have php5 selected when you are in the manage domains menu. If you are stuck there is a  
[http://wiki.habariproject.org/en/Installation:Dreamhost faq here.]

=== Change Permissions ===
In order to enjoy the most convenient installation process, it is required to make writable the folder where Habari will be installed.  The Habari installation process will create a <tt>config.php</tt> file and <tt>.htaccess</tt> file for you automatically.

If you are using a GNU/Linux (or other UNIX-like) host, you may apply the following chmod to the root folder of your Habari installation:
<pre>
chmod o+w .
</pre>
That allows everyone to write to the Habari directory.  Specifically, the web server will use this to create the files necessary for Habari's execution.

When the installation process completes, you may remove the "everyone" write permission with this:
<pre>
chmod o-w .
</pre>

==== Instructions for the paranoid ====
If the idea of giving everyone write access to your Habari directory gives you the willies, you can instead perform the following steps:
<pre>
touch .htaccess
touch config.php
chmod o+w .htaccess
chmod o+w config.php
</pre>
This creates empty <tt>.htaccess</tt> and <tt>config.php</tt> files, and makes only these files world-writable.  Habari will add the necessary bits to these files.

Upon completion, you may remove the world-writable permission with these commands:
<pre>
chmod o-w .htaccess
chmod o-w config.php
</pre>

The Habari installation process will do its best not to clobber any existing <tt>.htaccess</tt> file that might exist.

=== Activate PHP5 ===

Some hosts may offer both PHP4 and PHP5, but execute files with the <tt>.php</tt> extension using the PHP4 by default. You can often change this behavior by adding the following single line in the <tt>.htaccess</tt> file found in the root folder of your Habari installation:

<pre>AddHandler application/x-httpd-php5 .php </pre>

<em>Please read your host's FAQ as some hosts may require a different <tt>AddHandler</tt>.</em>

== Installing Habari ==

=== Step 1: Download and Extract ===

<em>As Habari is still in development, no guarantees are made that these are fully stable versions, or that updating won't break the current database structure.</em>

==== Developer Release ====
* Habari is still in development, however there is a developer release [http://habariproject.org/dist/ available for download].

==== SVN Checkout ====
<em>In order to use SVN you will need SSH access to your server. Some hosts have it enabled by default, but others may only enable it upon request.</em>

* Login to your web host.
<pre>ssh username@yourblog.com</pre>

Your host will then ask for your host password. Put it in and you are in. 

* Change directory to the one you wish to install Habari in. <em>Directory name may differ.</em>
<pre>cd ~/public_html/habari/</pre>

* Checkout the source code.
<pre>svn checkout http://svn.habariproject.org/habari/trunk/htdocs .</pre>

* If you are not in the Habari directory, use the following command to <em>checkout</em> in a new directory called <tt>habari</tt>.

<pre>svn checkout http://svn.habariproject.org/habari/trunk/htdocs habari</pre>

<em>Reference: [[Subversion and applying patchs]]</em>

===Step 2: Preparing the Database===
As mentioned, Habari requires a database from one of the supported formats.  If you are running on a professional hosting company (see [[Supported Hosts]]), chances are you either were provided with a database, or a web interface for creating a database. If that's the case, you can skip to the next step. Either way you are going to need:

* The name of the database
* The database user
* The password for that database user.

Please note that by default some third party hosts add your user name prior to these items.

===Step 3: Running the Installer===
After checking out the files, and creating your database, you are ready to run the installer.  Open your web browser and navigate to your install location, ie, <tt><nowiki>http://example.com/habari</nowiki></tt>.
You should be prompted with an installer, requiring you to choose your database type, and the necessary information to complete installation.

If the installer is not working properly, the [[Common Questions]] page has some information that may help you troubleshoot the installer.
====Database Host====
99% of the time, this is going to be <tt>localhost</tt>, and should be the default value in the installer.  

If this is not the case, your host should have provided you with the necessary information.  For example, if your installation is to be hosted on [http://dreamhost.com/ Dreamhost], then this value will be <tt>mysql.yourDomain.com</tt>.

====User Name====
This is the user name for the database, not your domain's user name.  If you created your database via the common C-Panel interface, remember this name is prefixed by the account, then the username, ie, <tt>user_dbusername</tt>.
====Password====
Again, this is the database user's password, not necessarily the account password.
====Database Name====
This is the name of the database you created for your installation.  As with the user, it's possible the database name is prefixed with your domain username, ie, <tt>user_databasename</tt>.
====Table Prefix====
<tt>habari__</tt> is the default, however if you are using multiple instances of habari in the same database, or have your own naming conventions, you can change the prefix here.

Fill in the remaining data with the name of your blog, an admin user name and password, and a default email.  Note that these can be changed later.

Click install, and if all goes well, you should be taken to the blog's home page.  You can then log in with the admin name and password you used on the installer page, and start blogging!

If you had any problems, you can post to the [http://groups.google.com/group/habari-users Habari user group].  Be sure to provide as many details as possible, including version of PHP, MySQL, and server config.

==Advanced Installation==
This procedure outlines a way to automate the installation process by predefining the <tt>.config</tt> file.

=== Predefined Configuration ===
Depending on your DBMS of choice, MySQL or SQLite, copy <tt><root>/system/schema/<dbms>/config.php</tt> to the root folder, where <tt>index.php</tt> is.

<em>Modify this array definition, or similar, in the copied <tt>config.php</tt> file.</em>
<pre>
$blog_data= array( 'admin_username' => 'ernie',
        'admin_pass1' => 'RubberDuck',
        'admin_pass2' => 'RubberDuck',
        'admin_email' => 'ernie@www.sesamestreetlive.com'
        );
</pre>

<em>Valid Parameters</em>
<pre>
        'db_user' => '',
        'db_pass' => '',
        'db_schema' => 'habari',
        'connection_string' => '',
        'table_prefix' => 'habari__',
        'admin_username' => 'admin',
        'admin_pass1' => '',
        'admin_pass2' => '',
        'blog_title' => 'I love Cookies',
        'admin_email' => 'bert@www.sesamestreetlive.com', 
</pre>

These values will be extracted when or if <tt>index.php</tt> detects that there are DBMS tables present.  In that case, the installer will not display the form but use these values instead.

This will allow a fully automated installation, without any prompts being displayed to the user (assuming the $db_connection array is properly constructed, too).

At the moment, it's a convenience for those who are constantly re-installing Habari and who don't want to waste a bunch of time keying in the same values over and over.  It's also the basis of an automagic deployment tool for hosting providers.

==[[Troubleshooting]]==

See [[Troubleshooting|troubleshooting]] page for more details...

==[[Installation/Special_Instructions|Special Instructions]]==
===Software Bundles===
*[[Installation/Special_Instructions/LAMP|LAMP]]
*[[Installation/Special_Instructions/MAMP|MAMP]]
*[[Installation/Special_Instructions/WAMP|WAMP]]
*[[Installation/Special_Instructions/Nginx|Nginx]]
*[[Installation/Special_Instructions/LiteSpeed|LiteSpeed]]

===Hosting Solutions===
*[[Installation/Special_Instructions/Dreamhost|Dreamhost]]
*[[Installation/Special_Instructions/Bluehost|Bluehost]]

[[Category:Manual]]
==Software Bundles==
*[[Installation/Special_Instructions/LAMP|LAMP]]
*[[Installation/Special_Instructions/MAMP|MAMP]]
*[[Installation/Special_Instructions/WAMP|WAMP]]
*[[Installation/Special_Instructions/Nginx|Nginx]]

==Hosting Solutions==
*[[Installation/Special_Instructions/A Small Orange|A Small Orange]]
*[[Installation/Special_Instructions/Dreamhost|Dreamhost]]
*[[Installation/Special_Instructions/Bluehost|Bluehost]]
A Small Orange does support PHP5, however, by default they use 4.x.  To enable PHP5, you need to add a single line to your .htaccess file for the directory in which you want to use PHP5.

<pre>AddHandler application/x-httpd-php5 .php</pre>
This should be added just above the Habari rules.
Also note, that ASO doesn't provide users with shell access by default.  You must request this feature via their excellent support team.
Early in 2008, [http://www.bluehost.com Bluehost] upgraded all servers to PHP 5.2.x which allows Habari to be installed and configured on a Bluehost server.

The other main pre-requisite for using Habari is the installation of the [http://uk.php.net/pdo PDO] (PHP Data Objects) extensions; specifically PDO drivers for the MySQL database.

To check the version of PHP running on a Bluehost server and to determine whether your server has the necessary software installed, simply create a text file named 'phpinfo.php' in the root directory of the Web server containing the following lines:

<pre>
<?php
phpinfo();
?>
</pre>

Then, visit the URL in a Web browser - <nowiki>http://www.mysite.com/phpinfo.php</nowiki> and examine the output.

The summary section includes the operating system of the host server, the exact version of the PHP interpreter and the options used to configure PHP.

[[Image:Bluehost-php-summary.png]]


* Check the version of PHP - Habari needs 5.2 (or higher)
* Check PDO and the required drivers for the MySQL database are installed. The Web page will be long so searching for 'PDO' may quickly locate the necessary section. If PDO and the drivers are present, you should see a section similar to the following:

[[Image:Bluehost-pdo.png]]

If the PDO extensions and the support for MySQL are present, proceed with the installation of Habari.

Otherwise, raise a ticket with Bluehost Technical Support asking for PDO and/or the drivers for MySQL to be installed on your server.
= How to install Habari on Dreamhost. =

== php5 ==

Log into your dreamhost.com account by going to [https://panel.dreamhost.com panel.dreamhost.com]. Find the ''Manage Domain'' setting and make a fully hosted domain with PHP version 5.2.2.

[[Image:s1.JPG]]

== MySQL ==

You then need to go to ''Goodies'' > ''Manage MySQL''. Scroll to the bottom of that page and find the bit that says ''Create a new MySQL database''. 

Make sure you create a new user and that you also change the ''hostname'' to the correct site. 

'''Example:'''
''Database name:'' Habari 
''Hostname:'' habari.example.com  
''User:'' habari2
''Password:'' habari12345 

[[Image:s2.JPG]]

If you like you can put a comment on the database so you know what is in there.  

Then follow the [[Installation|Install guides]] as normal.
== LiteSpeed ==

=== Configure PHP ===

==== Compile PDO Extensions ====
Using LiteSpeed's inbuilt compiler these options work for MySQL.
For sqlite you'll need to ensure you have sqlite installed on the system and use appropriate configure options.
<pre>
--with-mysql=/usr --with-mysql-sock=/var/run/mysqld/mysqld.sock --with-zlib --with-gd --enable-shmop --enable-track-vars --enable-sockets --enable-sysvsem
--enable-sysvshm --enable-magic-quotes --enable-mbstring --enable-pdo=shared --with-pdo-sqlite=shared --with-sqlite=shared --with-pdo-mysql=shared
--with-config-file-path=../php
</pre>

==== Load PDO Extensions ====
As we specified the location of the php config file in the configure line above to be ../php, this should map to /opt/lsws/php/php.ini.
Open that file and make sure the following is present
<pre>
;extension_dir = ./
extension=pdo.so
extension=pdo_mysql.so
</pre>
PHP will then use the extension_dir that is compiled into it.

To verify all is well create a new file info.php and inside put
<pre>
<?php phpinfo() ?>
</pre>
When you view this you should see the PDO extension and drivers enabled.

=== Configure Virtual Host ===

It's a lot simpler to deal with LiteSpeed as LiteSpeed rather using all your Apache knowledge and treat LSWS like Apache.
So don't use .htaccess just setup rewrite rules in the Virtual Host config.

Do everything you normally do for a php webapp. The part you need to take note of is the Rewrite tab.
Ensure Rewrite is enabled and that these rules are present.
<pre>
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteBase /
RewriteRule . index.php [PT]
</pre>
Update RewriteBase to where you have Habri installed. I've setup on the root dir.

Restart LiteSpeed and you should be good to install.
==Installing Habari to Run on Nginx [engine x]==

There are a few special considerations to get Habari up and running on [http://www.nginx.net/ Nginx].  The first being that Nginx does not provide FastCGI for you, so you've got to have a way to spawn your own FastCGI processes...

===Spawning FastCGI Processes===

I prefer to use the <code>spawn-fcgi</code> program provided with the [http://www.lighttpd.net/ lighttpd] web server.  You can use PHP's built-in FastCGI manager <code>php-cgi</code> to [http://blog.kovyrin.net/2006/05/30/nginx-php-fastcgi-howto/ do the same thing], but it's a bit more work and not as straight-forward.  Plus, if you learn how to use spawn-fcgi, you can use it for other FastCGI applications aside from ones that are PHP-based.

====Install spawn-fcgi====

To download and install <code>spawn-fcgi</code> to your machine, run the following commands.  Feel free to adjust the path where you want the binary and don't worry, all of the building happens in your current directory...nothing else will be installed on your machine.

<pre>$ wget http://www.lighttpd.net/download/lighttpd-1.4.18.tar.bz2
$ tar -xvjf lighttpd-1.4.18.tar.bz2
$ cd lighttpd-1.4.18/
$ ./configure
$ make
$ sudo cp src/spawn-fcgi /usr/bin/spawn-fcgi</pre>

''NOTE: You will need to be root in order to copy the binary to its final location, everything else should work fine as a normal user.  To gain root privileges, the program <code>sudo</code> was used in the example above, you may or may not have access to sudo on your machine.''

After <code>spawn-fcgi</code> has been copied to the desired location, you can safely remove the build directory and original source file:

<pre>$ cd ..
$ rm -rf lighttpd-1.4.18/
$ rm lighttpd-1.4.18.tar.bz2</pre>

====Run spawn-fcgi====

This part will be fairly distribution-specific, but I'll provide the basic command that you'll need.  What you'll want to do is find a way to run this command as part of your init scripts so the processes will be spawned automatically when you reboot your server.

<pre>/usr/bin/spawn-fcgi -f /usr/bin/php-cgi -a 127.0.0.1 -p 53217 -P /var/run/habari.pid</pre>

* <code>-f</code> -> the filename of the fcgi-application; in our case we want php-cgi, which is provided by your distribution's php package.  If you don't know where to find it, try running <code>which php-cgi</code> on the command line.
* <code>-a</code> -> the address to bind the processes to; in our case we want the localhost
* <code>-p</code> -> the port number to bind the processes to; pick whatever you want (technically it would be best to pick one between 49152 and 65535), just make sure to remember the number and use that same one for your Nginx configuration file
* <code>-P</code> -> the location where to save the process id file; you can use this file to easily [[Installation/Special_Instructions/Nginx#How to Properly Kill spawn-fcgi Processes|kill the processes]] later

For better security, you can also set the user/group that the spawned processes will run as to a non-privileged user.  To do this, you use the <code>-u</code> and <code>-g</code> flags respectively.  For more information on all the available options, run <code>spawn-fcgi -h</code> on the command line.

===Configure Nginx===

The key part here is that Habari uses the FrontController design pattern, and thus requires any unrecognized URL request (meaning a request for a file that does not exist on the server) to be forwarded to and handled by the main index.php file.  The file you need to edit is called <code>nginx.conf</code> and is installed in different places depending on your Linux distribution.  For me, it was located here: <code>/etc/nginx/conf/nginx.conf</code> (installed from source, the default location is <code>/usr/local/nginx/conf/nginx.conf</code>)

<pre>server {
    listen       12.345.678.90:80;  # YOUR PUBLIC IP ADDRESS (just use listen 80 to bind to all interfaces)
    server_name  domain.com;        # YOUR DOMAIN NAME (subdomains [blog.yourdomain.com] will also work here)

    root   /srv/www/nginx/domain.com/habari;  # ABSOLUTE PATH TO YOUR HABARI INSTALLATION

    location / {
        index  index.php index.html index.htm;

        # serve static files that exist without running other rewrite tests
        if (-f $request_filename) {
            expires  30d;
            break;
        }

        # send all non-existing file or directory requests to index.php
        # GOTCHA: Make sure your rewrite rules are inside the location block. 
        if (!-e $request_filename) {
            rewrite  ^  /index.php  last;
        }
    }

    location ~ \.php$ {
        fastcgi_pass   localhost:53217;  # PORT NUMBER WHERE YOU SPAWNED YOUR FCGI PROCESSES
        fastcgi_index  index.php;

        fastcgi_param  SERVER_SOFTWARE  nginx;
        fastcgi_param  QUERY_STRING     $query_string;
        fastcgi_param  REQUEST_METHOD   $request_method;
        fastcgi_param  CONTENT_TYPE     $content_type;
        fastcgi_param  CONTENT_LENGTH   $content_length;
        fastcgi_param  SCRIPT_NAME      $fastcgi_script_name;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;  # SAME PATH AS ABOVE
        fastcgi_param  REQUEST_URI      $request_uri;
        fastcgi_param  REDIRECT_STATUS  200;
    }
}</pre>

After you've added these settings to your configuration, restart the <code>nginx</code> daemon and your settings should take effect.

===How to Properly Kill spawn-fcgi Processes===

If you need to kill the FastCGI processes that you spawned (for instance, when stopping an init script), then you can run the following command since we saved the process id to a file:

<pre>kill $(cat /var/run/habari.pid) &>/dev/null</pre>
[http://www.wampserver.com/en/ WampServer] is a Windows software package containing Apache, PHP and mySQL that enables users to easily install Habari on Windows.

== Install WampServer ==

Download and install the WampServer package. Check the basic operation of the WAMP package by starting all WAMP Services and accessing the Web site.

<nowiki>http://localhost/</nowiki>

The default WampServer page displaying the Apache and PHP version should be displayed.

[[Image:WampServer.PNG]]

== Create a mySQL database ==

Create a mySQL database with an associated database account for use with Habari. Use the phpMyAdmin utility. Select 'Privileges' to create a new user e.g. 'habari'.

Check the option 

'Create database with same name and grant all privileges'

Make a note of the username and password credentials.

== Configure Apache ==

The default settings for the Apache Web Server bundled with WampServer result in a '500 Internal Server error' when trying to install Habari.

''Internal Server Error

''The server encountered an internal error or misconfiguration and was unable to complete your request.''

''Please contact the server administrator, webmaster@localhost and inform them of the time the error occurred, and anything you might have done that may have caused the error.''

''More information about this error may be available in the server error log.

When the Apache error log (Apache-Apache error log) is examined, the following Apache error is recorded

''D:/wamp/www/habari-0.4/.htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration''

To fix this problem, simply enable the Apache Rewrite module from the WAMP menu (Apache-Apache Modules). Scroll down the list and ensure 'rewrite_module' is checked.

After this configuration change, all Wamp services should be stopped and restarted (Restart All Services). Installation of Habari should now complete successfully.

== Install Habari ==

Download the Habari [http://habariproject.org/en/download distribution] and extract the files to the Web Server root directory (D:\wamp\www\habari).

Bring up the Habari installation page in a Web browser - <nowiki>http://localhost/habari/</nowiki> - and simply follow the standard Habari installation process.
Early in 2008, [http://www.bluehost.com Bluehost] upgraded all servers to PHP 5.2.x which allows Habari to be installed and configured on a Bluehost server.

The other main pre-requisite for using Habari is the installation of the [http://uk.php.net/pdo PDO] (PHP Data Objects) extensions; specifically PDO drivers for the MySQL database.

To check the version of PHP running on a Bluehost server and to determine whether your server has the necessary software installed, simply create a text file named 'phpinfo.php' in the root directory of the Web server containing the following lines:

<pre>
<?php
phpinfo();
?>
</pre>

Then, visit the URL in a Web browser - <nowiki>http://www.mysite.com/phpinfo.php</nowiki> and examine the output.

The summary section includes the operating system of the host server, the exact version of the PHP interpreter and the options used to configure PHP.

[[Image:Bluehost-php-summary.png]]


* Check the version of PHP - Habari needs 5.2 (or higher)
* Check PDO and the required drivers for the MySQL database are installed. The Web page will be long so searching for 'PDO' may quickly locate the necessary section. If PDO and the drivers are present, you should see a section similar to the following:

[[Image:Bluehost-pdo.png]]

If the PDO extensions and the support for MySQL are present, proceed with the installation of Habari.

Otherwise, raise a ticket with Bluehost Technical Support asking for PDO and/or the drivers for MySQL to be installed on your server.

These documents are intended for basic information on installing, upgrading and base use. As development continues, additional information will be added. For more
comprehensive documentation, visit the [http://wiki.habariproject.org/en/Main_Page main Habari wiki].

==About Habari== 
Habari intends to represent a fresh start to the idea of blogging. The system will be fast, easy to use, and easy to modify. New users should have no
problem using and enjoying Habari. Advanced users should have no problem tweaking Habari to do exactly what they need it to do.

==What Does Habari Mean?== 
"Habari" is a Swahili word that means "what's the news?" It's similar to the English "What's up?" or the Spanish "¿Qué pasa?" The typical
response is "mzuri sana", which means "very good."

==How Will it be Different?== 
Habari is being written with a firm understanding of the current state of blogging. Most other blogging packages have been around long
enough that their responses to things like comment spam and Digg site overloads are bolted on after the fact; whereas Habari is being written from the beginning to
take these things -- and more -- into account.

Habari strongly favors open, standard, and documented protocols. Atom, being both open and documented, is the prefered syndication format, and the Atom Publishing
Protocol is the prefered means of remote communication with your site. This is a core feature, and not a plugin.

Habari is being written specifically for modern web hosting environments, and uses modern object-oriented programming techniques. Using these recent but
well-established additions to the PHP language allows Habari to make use of PDO, enabling prepared statements for all interactions with the database. This greatly
reduces the system's vulnerability to SQL injection attacks. This is just one of many benefits of modern object-oriented techniques.

Those are just a few of the technical differences, but a major component of what makes Habari different is its community participation model. Users who demonstrate a
consistent level of quality contributions to the project are granted more privileges within the project.

==Our Process==
===Meritocracy=== 
Following the [http://www.apache.org/foundation/how-it-works.html#meritocracy/ meritocracy] model advocated by the Apache Software
Foundation, the Habari project rewards contributors with decision making privilege in the project. Regular participants should be able to influence the direction of
the project to which they dedicate their time and talent. The requirement for participation will be something of a moving target over time, but the barrier to entry
should never be so high that dedicated people are excluded.

===Transparency===
A fundamental aspect of successful open source projects is transparency. Decisions are made in the public's eye, and discussion and deliberation
takes place in the open. The Habari project pursues transparency by using the Google issues tracker to maintain a record of proposed changes, and discussion
associated with those changes. All proposals -- whether for code, documentation, visual style, etc -- are logged through the issues tracker for public scrutiny and
input.

===Licensing=== 
All contributions to Habari will acquire the Apache License, which is a modified version of the [http://www.apache.org/licenses/LICENSE-2.0 Apache
License]. For those unfamiliar with this license, the [http://www.apache.org/foundation/licence-FAQ.html Apache License FAQ
page] should answer most of your questions.

Developers contributing to the Habari project itself should note that, unless explicitly stated otherwise, any contribution intentionally submitted for inclusion
shall be under the terms and conditions of the Habari license, without any additional terms or conditions. However, plugins and themes designed to work with Habari
are not required to have the same license as Habari itself.

You can find our license included in the source, and in our [http://trac.habariproject.org/habari/browser/trunk/LICENSE code repository].
[[Introduction]]
[[What's New]]
[[Installation]]
[[Upgrading]]
[[FAQ]]
[[User Documentation]]
[[Developer Documentation]]
/***
|''Name:''|MediaWikiFormatterPlugin|
|''Description:''|Allows Tiddlers to use [[MediaWiki|http://meta.wikimedia.org/wiki/Help:Wikitext]] ([[WikiPedia|http://meta.wikipedia.org/]]) text formatting|
|''Author:''|Martin Budden (mjbudden (at) gmail (dot) com)|
|''Source:''|http://www.martinswiki.com/#MediaWikiFormatterPlugin |
|''CodeRepository:''|http://svn.tiddlywiki.org/Trunk/contributors/MartinBudden/formatters/MediaWikiFormatterPlugin.js |
|''Version:''|0.4.3|
|''Date:''|Jul 27, 2007|
|''Comments:''|Please make comments at http://groups.google.co.uk/group/TiddlyWikiDev |
|''License:''|[[Apache Software License 2.0|http://www.apache.org/licenses/LICENSE-2.0.html]]|
|''~CoreVersion:''|2.1.0|

|''Display instrumentation''|<<option chkDisplayInstrumentation>>|
|''Display empty template links:''|<<option chkMediaWikiDisplayEmptyTemplateLinks>>|
|''Allow zooming of thumbnail images''|<<option chkMediaWikiDisplayEnableThumbZoom>>|
|''List references''|<<option chkMediaWikiListReferences>>|
|''Display unsupported magic words''|<<option chkDisplayMediaWikiMagicWords>>|

This is the MediaWikiFormatterPlugin, which allows you to insert MediaWiki formated text into a TiddlyWiki.

The aim is not to fully emulate MediaWiki, but to allow you to work with MediaWiki content off-line and then resync the content with your MediaWiki later on, with the expectation that only minor edits will be required.

To use MediaWiki format in a Tiddler, tag the Tiddler with MediaWikiFormat or set the tiddler's {{{wikiformat}}} extended field to {{{mediawiki}}}.

!!!Issues
There are (at least) the following known issues:
# Not all styles from http://meta.wikimedia.org/wiki/MediaWiki:Common.css incorporated
## Styles for tables don't yet match Wikipedia styles.
## Styles for image galleries don't yet match Wikipedia styles.
# Anchors not yet supported.

!!!Not supported
# Template parser functions (also called colon functions) http://meta.wikimedia.org/wiki/ParserFunctions eg &#123;&#123; #functionname: argument 1 | argument 2 | argument 3... &#125;&#125;
# Magic words and variables http://meta.wikimedia.org/wiki/Help:Magic_words eg {{{__TOC__}}}, &#123;&#123;CURRENTDAY&#125;&#125;, &#123;&#123;PAGENAME&#125;&#125;
# {{{^''}}} (italic at start of line) indents, makes italic and quotes with guilmot quote

!!!No plans to support
# Template substitution on save http://meta.wikimedia.org/wiki/Help:Substitution eg &#123;&#123; subst: templatename &#125;&#125;

***/

//{{{
// Ensure that the MediaWikiFormatter Plugin is only installed once.
if(!version.extensions.MediaWikiFormatterPlugin) {
version.extensions.MediaWikiFormatterPlugin = {installed:true};

if(version.major < 2 || (version.major == 2 && version.minor < 1))
	{alertAndThrow('MediaWikiFormatterPlugin requires TiddlyWiki 2.1 or later.');}

if(config.options.chkDisplayInstrumentation == undefined)
	{config.options.chkDisplayInstrumentation = false;}

if(config.options.chkMediaWikiDisplayEmptyTemplateLinks == undefined)
	{config.options.chkMediaWikiDisplayEmptyTemplateLinks = false;}
if(config.options.chkMediaWikiDisplayEnableThumbZoom == undefined)
	{config.options.chkMediaWikiDisplayEnableThumbZoom = false;}
if(config.options.chkMediaWikiListReferences == undefined)
	{config.options.chkMediaWikiListReferences = false;}
if(config.options.chkDisplayMediaWikiMagicWords == undefined)
	{config.options.chkDisplayMediaWikiMagicWords = false;}

//#config.textPrimitives.urlPattern = "(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?/{0,2}[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?";
//#config.textPrimitives.urlPattern = "[a-z]{3,8}:/{0,2}[^\\s:/<>'\"][^\\s/<>'\"]*(?:/|\\b)";

//<div class='viewer' macro='view text wikified'></div>;

config.macros.include = {};
config.macros.include.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
	if((tiddler instanceof Tiddler) && params[0]) {
		var host = store.getValue(tiddler,'server.host');
		if(host && host.indexOf('wikipedia')!=-1) {
			var t = store.fetchTiddler(params[0]);
			var text = store.getValue(t,'text');
			wikify(text,place,highlightHack,tiddler);
		}
	}
};


MediaWikiFormatter = {}; // 'namespace' for local functions

mwDebug = function(out,str)
{
	createTiddlyText(out,str.replace(/\n/mg,'\\n').replace(/\r/mg,'RR'));
	createTiddlyElement2(out,'br');
};

MediaWikiFormatter.Tiddler_changed = Tiddler.prototype.changed;
Tiddler.prototype.changed = function()
{
	if((this.fields.wikiformat==config.parsers.mediawikiFormatter.format) || this.isTagged(config.parsers.mediawikiFormatter.formatTag)) {
		//# update the links array, by checking for MediaWiki format links
		this.links = [];
//#lookaheadRegExp: /\[\[(?:([a-z]{2,3}:)?)(#?)([^\|\]]*?)(?:(\]\](\w*))|(\|(.*?)\]\]))/mg,
		var tiddlerLinkRegExp = /\[\[(?::?([A-Za-z]{2,}:)?)(#?)([^\|\]]*?)(?:(\]\])|(\|(.*?)\]\]))/mg;
		tiddlerLinkRegExp.lastIndex = 0;
		var match = tiddlerLinkRegExp.exec(this.text);
		while(match) {
			if(!match[1] && !match[2])
				this.links.pushUnique(match[3]);
			match = tiddlerLinkRegExp.exec(this.text);
		}
	} else if(!this.isTagged('systemConfig')) {
		MediaWikiFormatter.Tiddler_changed.apply(this,arguments);
		return;
	}
	this.linksUpdated = true;
};

TiddlyWiki.prototype.getMediaWikiPagesInNamespace = function(namespace)
{
	var results = [];
	this.forEachTiddler(function(title,tiddler) {
		if(tiddler.title.indexOf(namespace)==0)
			results.push(tiddler);
		});
	results.sort(function(a,b) {return a.title < b.title ? -1 : +1;});
	return results;
};

TiddlyWiki.prototype.getMediaWikiPages = function()
{
	var results = [];
	this.forEachTiddler(function(title,tiddler) {
		if(!tiddler.isTagged('excludeLists') && tiddler.title.indexOf(':')==-1)
			results.push(tiddler);
		});
	results.sort(function(a,b) {return a.title < b.title ? -1 : +1;});
	return results;
};

TiddlyWiki.prototype.getMediaWikiOtherPages = function()
{
	var results = [];
	this.forEachTiddler(function(title,tiddler) {
		if(!tiddler.isTagged('excludeLists') && tiddler.title.indexOf(':')!=-1)
			results.push(tiddler);
		});
	results.sort(function(a,b) {return a.title < b.title ? -1 : +1;});
	return results;
};

config.macros.list.otherpages = {};
config.macros.list.otherpages.handler = function(params)
{
	return store.getMediaWikiOtherPages();
};

config.macros.list.templates = {};
config.macros.list.templates.handler = function(params)
{
	return store.getMediaWikiPagesInNamespace('Template:');
};

config.macros.list.categories = {};
config.macros.list.categories.handler = function(params)
{
	return store.getMediaWikiPagesInNamespace('Category:');
};

wikify = function(source,output,highlightRegExp,tiddler)
{
	if(source && source != '') {
		var w = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
		w.linkCount = 0;
		w.tableDepth = 0;
		w.output = tiddler==null ? output : createTiddlyElement2(output,'p');
		var t1,t0 = new Date();
		w.subWikifyUnterm(w.output);
		if(tiddler && config.options.chkDisplayInstrumentation) {
			t1 = new Date();
			var t = tiddler ? tiddler.title : source.substr(0,10);
			displayMessage('Wikify "'+t+'" in ' + (t1-t0) + ' ms');
		}
	}
//#at point of usage can use:
//#var output = w.output.nodeType==1 && w.output.nodeName=='P' ? w.output.parentNode : w.output;
};

function createTiddlyElement2(parent,element)
{
	var e = document.createElement(element);
	parent.appendChild(e);
	return e;
}

config.formatterHelpers.createElementAndWikify = function(w)
{
	w.subWikifyTerm(createTiddlyElement2(w.output,this.element),this.termRegExp);
};

MediaWikiFormatter.hijackListAll = function ()
{
	MediaWikiFormatter.oldListAll = config.macros.list.all.handler;
	config.macros.list.all.handler = function(params) {
		return store.getMediaWikiPages();
	};
};
MediaWikiFormatter.hijackListAll();

MediaWikiFormatter.normalizedTitle = function(title)
{
	title = title.trim();
	var n = title.charAt(0).toUpperCase() + title.substr(1);
	return n.replace(/\s/g,'_');
};

//# see http://meta.wikimedia.org/wiki/Help:Variable
MediaWikiFormatter.expandVariable = function(w,variable)
{
	switch(variable) {
	case 'PAGENAME':
		createTiddlyText(w.output,w.tiddler.title);
		break;
	case 'PAGENAMEE':
		createTiddlyText(w.output,MediaWikiFormatter.normalizedTitle(w.tiddler.title));
		break;
	case 'REVISIONID':
		var text = w.tiddler.fields['server.revision'];
		if(text)
			createTiddlyText(w.output,text);
		break;
	default:
		return false;
	}
	return true;
};

MediaWikiFormatter.getTemplateParams = function(text)
{
//#{{test|a|b}}
//#{{test|n=a|m=b}}
	var params = {};

	text += '|';
	var pRegExp = /(?:([^\|]*)=)?([^\|]*)\|/mg;
	var match = pRegExp.exec(text);
	if(match) {
		//# skip template name
		match = pRegExp.exec(text);
	}
	var i = 1;
	while(match) {
		//#params[match[1] ? match[1] : i++] = match[2];
		if(match[1]) {
			params[match[1]] = match[2];
		} else {
			params[i] = match[2];
			i++;
		}
		match = pRegExp.exec(text);
	}
	return params;
};

//#The #if function is an if-then-else construct. The syntax is:
//#	{{#if: <condition> | <then text> | <else text> }}
//#	{{#if: <condition> | <then text> }}

//#If the condition is an empty string or consists only of whitespace, then it is considered false, and the ''else text'' is returned. Otherwise, the ''then text'' is returned. The ''else text'' may be omitted, in which case the result will be blank if the condition is false.

//#An example:
//#	{{#if| {{{parameter|}}} | Parameter is defined. | Parameter is undefined, or empty}}

//#Note that the {{#if}} function does '''not''' support "=" signs or mathematical expressions.
//#	{{#if|1 = 2 | yes | no}} will return "yes", because the string "1 = 2" is not blank.
//#	It is intended as an ''"if not empty"'' structure.

//# {{#if:{{{param1|}}} | param1value:{{{param1}}} }}
//# include {{testTpf|param1=hello}}
//# becomes:
//# {{#if:hello | param1value:hello }}
//# becomes:
//# param1value:hello

//# include {{testTpf}}
//# becomes:
//# {{#if: | param1value: }}
//# becomes:
//# <nothing>


//# see:
//# http://meta.wikimedia.org/wiki/ParserFunctions
//# http://www.mediawiki.org/wiki/Extension:Parser_function_extensions
//# http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/ParserFunctions/ParserFunctions.php?view=markup
//# http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Parser.php?view=markup
MediaWikiFormatter.evaluateTemplateParserFunctions = function(text)
{
//#displayMessage("evtpf:"+text);
//#if(text=="{{#if:hello | param1value=hello }}")
//#	return "param1value=hello";
//#	return text;
	var fnRegExp = /\{\{#if:([^\|]*?)\|([^\|]*?)(?:\|(.*?))?\}\}/mg;
	var t = '';
	var fi = 0;
	match = fnRegExp.exec(text);
	while(match) {
//#displayMessage("m:"+match);
//#displayMessage("m0:"+match[0]);
//#displayMessage("m1:"+match[1]);
//#displayMessage("m2:"+match[2]);
//#displayMessage("m3:"+match[3]);
//#displayMessage("ss:"+text.substring(fi,match.index));
		t += text.substring(fi,match.index);
		var m = match[1] ? match[1].trim() : null;
		if(m)
			t += match[2];
		else if(match[3])
			t += match[3].trim();
		fi = fnRegExp.lastIndex;
		match = fnRegExp.exec(text);
	}
	t += text.substring(fi);
	text = t == '' ? text : t;
//#displayMessage("text:"+text);
	return text;
};

MediaWikiFormatter.expandTemplate = function(w,templateText,params)
//# Expand the template by dealing with <noinclude>, <includeonly> and substituting parameters with their values
//# see http://meta.wikimedia.org/wiki/Help:Template
{
//#mwDebug(w.output,'et:'+templateText);
//#displayMessage("expandTemplate:"+templateText);
	var text = templateText;
	text = text.replace(/<noinclude>((?:.|\n)*?)<\/noinclude>/mg,'');// remove text between noinclude tags
	var includeOnlyRegExp = /<includeonly>((?:.|\n)*?)<\/includeonly>/mg;
	var t = '';
	var match = includeOnlyRegExp.exec(text);
	while(match) {
		t += match[1];
		match = includeOnlyRegExp.exec(text);
	}
	text = t == '' ? text : t;

	var paramsRegExp = /\{\{\{(.*?)(?:\|(.*?))?\}\}\}/mg;
	t = '';
	var pi = 0;
	match = paramsRegExp.exec(text);
	while(match) {
		var name = match[1];
		var val = params[name];
		if(!val) {
			//# use default
			val = match[2];
		}
		if(!val) {
			//# if no value or default, parameter evaluates to name
			val = '';//val = match[0];
		}
		t += text.substring(pi,match.index) + val;
		pi = paramsRegExp.lastIndex;
		match = paramsRegExp.exec(text);
	}
	return t == '' ? text : t;
/*	//displayMessage("ss:"+text.substring(pi));
	t += text.substring(pi);
	t = MediaWikiFormatter.evaluateTemplateParserFunctions(t);
	//{{#if: {{{perihelion|}}} | <tr><th>[[Perihelion|Perihelion distance]]:</th><td>{{{perihelion}}}</td></tr>}}
	//{{#if:{{{symbol|}}} | {{{symbol}}} | }}
	text = t == '' ? text : t;
	displayMessage("t2:"+text);
	return text;
*/
};

MediaWikiFormatter.endOfParams = function(w,text)
{
	var p = 0;
	var i = text.indexOf('|');
	if(i==-1) {return -1;}
	var n = text.indexOf('\n');
	if(n!=-1 && n<i) {return -1;}
	var b = text.indexOf('[[');
	//# can't have [[ in parameters
	if(b!=-1 && b<i) {return -1;}
	
	b = text.indexOf('{{');
	while(b!=-1 && b<i) {
		//# have {{ before |, so need to find first '|' after '{{..}}' pairs
		//# cut off the ..{{, find the }} cut off and repeat
		p += b;
		text = text.substr(b);
		var c = text.indexOf('}}');
		p += c;
		text = text.substr(c);
		i = text.indexOf('|');
		if(i==-1) {return -1;}
		n = text.indexOf('\n');
		if(n!=-1 && n<i) {return -1;}
		b = text.indexOf('{{');
		i = -1;
	}
	return i;
};

MediaWikiFormatter.readToDelim = function(w)
//!!! this is a bit rubish, needs doing properly.
{
//#delimiter, startBracket terminatorBracket
	var dRegExp = /\|/mg;
	var sRegExp = /\[\[/mg;
	var tRegExp = /\]\]/mg;

	dRegExp.lastIndex = w.startMatch;
	var dMatch = dRegExp.exec(w.source);
	sRegExp.lastIndex = w.startMatch;
	var sMatch = sRegExp.exec(w.source);
	tRegExp.lastIndex = w.startMatch;
	var tMatch = tRegExp.exec(w.source);
	if(!tMatch) {
		//#mwDebug(w.output,'ERROR1');
		return false;
	}

	while(sMatch && sMatch.index<tMatch.index) {
		if(dMatch && dMatch.index<sMatch.index) {
			//# delim is before startBracket, so return it
//#mwDebug(w.output,'di:'+dMatch.index+' dl:'+sRegExp.lastIndex);
			w.nextMatch = dRegExp.lastIndex;
			w.matchLength = dMatch.index - w.startMatch;
			return true;
		}
//#mwDebug(w.output,'si:'+sMatch.index+' sl:'+sRegExp.lastIndex);
//#mwDebug(w.output,'ti:'+tMatch.index+' tl:'+tRegExp.lastIndex);
		//# startBracket before termBracket, so skip over bracket pairs
		//# found eg [[, so look for ]]
		tRegExp.lastIndex = sRegExp.lastIndex;
		tMatch = tRegExp.exec(w.source);
//#mwDebug(w.output,'xti:'+tMatch.index+' tl:'+tRegExp.lastIndex);
		
		//# and look for another [[
		w.nextMatch = tRegExp.lastIndex;
		dRegExp.lastIndex = w.nextMatch;
		dMatch = dRegExp.exec(w.source);
		sRegExp.lastIndex = w.nextMatch;
		sMatch = sRegExp.exec(w.source);
		tRegExp.lastIndex = w.nextMatch;
		tMatch = tRegExp.exec(w.source);
	}
		
	if(dMatch && dMatch.index<tMatch.index) {
		//# delim is before term, so return it
//#mwDebug(w.output,'2di:'+dMatch.index+' dl:'+sRegExp.lastIndex);
		w.nextMatch = dRegExp.lastIndex;
		w.matchLength = dMatch.index - w.startMatch;
		return true;
	}
	if(tMatch) {
		//# delim is before term, so return it
//#mwDebug(w.output,'2ti:'+tMatch.index+' tl:'+tRegExp.lastIndex);
		w.nextMatch = tRegExp.lastIndex;
		w.matchLength = tMatch.index - w.startMatch;
		return false;
	}
	//#mwDebug(w.output,'ERROR2');
	//# return term
	w.nextMatch = tRegExp.lastIndex;
	w.matchLength = -1;
	return false;
};

MediaWikiFormatter.getParams = function(w)
{
	var params = [];
	var i = 1;
	w.startMatch = w.nextMatch;
	var read = MediaWikiFormatter.readToDelim(w);
	if(w.matchLength!=-1) {
		params[i] = w.source.substr(w.startMatch,w.matchLength);
	}
	while(read) {
		i++;
		w.startMatch = w.nextMatch;
		read = MediaWikiFormatter.readToDelim(w);
		if(w.matchLength!=-1) {
			params[i] = w.source.substr(w.startMatch,w.matchLength);
		}
	}
	return params;
};

MediaWikiFormatter.setFromParams = function(w,p)
{
	var r = {};
	var re = /\s*(.*?)=(?:(?:"(.*?)")|(?:'(.*?)')|((?:\w|%|#)*))/mg;
	var match = re.exec(p);
	while(match)
		{
		var s = match[1].unDash();
		if(match[2]) {
			r[s] = match[2];
		} else if(match[3]) {
			r[s] = match[3];
		} else {
			r[s] = match[4];
		}
		match = re.exec(p);
	}
	return r;
};

MediaWikiFormatter.setAttributesFromParams = function(e,p)
{
	var re = /\s*(.*?)=(?:(?:"(.*?)")|(?:'(.*?)')|((?:\w|%|#)*))/mg;
	var match = re.exec(p);
	while(match) {
		var s = match[1].unDash();
		if(s == 'bgcolor') {
			s = 'backgroundColor';
		}
		try {
			if(match[2]) {
				e.setAttribute(s,match[2]);
			} else if(match[3]) {
				e.setAttribute(s,match[3]);
			} else {
				e.setAttribute(s,match[4]);
			}
		}
		catch(ex) {}
		match = re.exec(p);
	}
};

config.mediawiki = {};
config.mediawiki.formatters = [
{
	name: 'mediaWikiHeading',
	match: '^={1,6}(?!=)\\n?',
	termRegExp: /(={1,6}\n?)/mg,
	handler: function(w)
	{
		//#var output = w.output.nodeType==1 && w.output.nodeName=='P' ? w.output.parentNode : w.output;
		var output = w.output;
		var e = createTiddlyElement2(output,'h' + w.matchLength);
		//# drop anchor
		var a = createTiddlyElement2(e,'a');
		var t = w.tiddler ? MediaWikiFormatter.normalizedTitle(w.tiddler.title) + ':' : '';
		var len = w.source.substr(w.nextMatch).indexOf('=');
		a.setAttribute('name',t+MediaWikiFormatter.normalizedTitle(w.source.substr(w.nextMatch,len)));
		w.subWikifyTerm(e,this.termRegExp);
		//#w.output = createTiddlyElement2(output,'p');
	}
},

{
	name: 'mediaWikiTable',
	match: '^\\{\\|', // ^{|
	tableTerm: '\\n\\|\\}', // |}
	rowStart: '\\n\\|\\-', // \n|-
	cellStart: '\\n!|!!|\\|\\||\\n\\|', //\n! or !! or || or \n|
	caption: '\\n\\|\\+',
	rowTerm: null,
	cellTerm: null,
	inCellTerm: null,
	tt: 0,
	debug: null,
	rowTermRegExp: null,
	handler: function(w)
	{
		if(!this.rowTermRegExp) {
			this.rowTerm = '(' + this.tableTerm +')|(' + this.rowStart + ')';
			this.cellTerm = this.rowTerm + '|(' + this.cellStart + ')';
			this.inCellTerm = '(' + this.match + ')|' + this.rowTerm + '|(' + this.cellStart + ')';
			this.caption = '(' + this.caption + ')|' + this.cellTerm;

			this.rowTermRegExp = new RegExp(this.rowTerm,'mg');
			this.cellTermRegExp = new RegExp(this.cellTerm,'mg');
			this.inCellTermRegExp = new RegExp(this.inCellTerm,'mg');
			this.captionRegExp = new RegExp(this.caption,'mg');
		}
//#this.debug = createTiddlyElement2(w.output,'p');
//#mwDebug(this.debug,'start table');
		this.captionRegExp.lastIndex = w.nextMatch;
		var match = this.captionRegExp.exec(w.source);
		if(!match) {return;}
		//#var inPara = w.output.nodeType==1 && w.output.nodeName=='P' ? true : false;
		//#var output = inPara ? w.output.parentNode : w.output;
		var output = w.output;
		var table = createTiddlyElement2(output,'table');
		var rowContainer = table;

		var i = w.source.indexOf('\n',w.nextMatch);
		if(i>w.nextMatch) {
			MediaWikiFormatter.setAttributesFromParams(table,w.source.substring(w.nextMatch,i));
			w.nextMatch = i;
		}

		var rowCount = 0;
		var eot = false;
		if(match[1]) {
			//# caption
			var caption = createTiddlyElement2(table,'caption');
			w.nextMatch = this.captionRegExp.lastIndex;
			var captionText = w.source.substring(w.nextMatch);
			var n = captionText.indexOf('\n');
			captionText = captionText.substr(0,n);
			i = MediaWikiFormatter.endOfParams(w,captionText);
			if(i!=-1) {
				captionText = w.source.substr(w.nextMatch,i);
				//#captionText = captionText.replace(/^\+/mg,'')//!!hack until I fix this properly
				//#MediaWikiFormatter.setAttributesFromParams(caption,captionText);
				w.nextMatch += i+1;
			}
			if(caption != table.firstChild) {
				table.insertBefore(caption,table.firstChild);
			}
			w.subWikify(caption,this.cellTerm);
			//# rewind to before the match
			w.nextMatch -= w.matchLength;
			this.cellTermRegExp.lastIndex = w.nextMatch;
			var match2 = this.cellTermRegExp.exec(w.source);
			if(match2) {
				if(match2[3]) {
					//# no first row marker
					eot = this.rowHandler(w,createTiddlyElement2(rowContainer,'tr'));
					rowCount++;
				}
			}
		} else if(match[3]) {
			//# row
			//# rewind to before the match
			w.nextMatch = this.captionRegExp.lastIndex-match[3].length;
		} else if(match[4]) {
			//# cell, no first row marker in table
			//# rewind to before the match
			w.nextMatch = this.captionRegExp.lastIndex-match[4].length;
			eot = this.rowHandler(w,createTiddlyElement2(rowContainer,'tr'));
			rowCount++;
		}

		this.rowTermRegExp.lastIndex = w.nextMatch;
		match = this.rowTermRegExp.exec(w.source);
		while(match && eot==false) {
			if(match[1]) {
				//# end table
				w.nextMatch = this.rowTermRegExp.lastIndex;
				if(w.tableDepth==0) {
					return;
				}
			} else if(match[2]) {
				//# row
				var rowElement = createTiddlyElement2(rowContainer,'tr');
				//# skip over the match
				w.nextMatch += match[2].length;
				i = w.source.indexOf('\n',w.nextMatch);
				if(i>w.nextMatch) {
					MediaWikiFormatter.setAttributesFromParams(rowElement,w.source.substring(w.nextMatch,i));
					w.nextMatch = i;
				}
				eot = this.rowHandler(w,rowElement);
			}
			rowCount++;
			this.rowTermRegExp.lastIndex = w.nextMatch;
			match = this.rowTermRegExp.exec(w.source);
		}//# end while
		if(w.tableDepth==0) {
			//# skip over tableterm, \n|}
			w.nextMatch +=3;
		}
		//#if(inPara)
		//#	w.output = createTiddlyElement2(output,'p');
	},//# end handler

	rowHandler: function(w,e)
	{
		//# assumes w.nextMatch points to first cell terminator, returns false if any improperly terminated element
		var cell;
		this.inCellTermRegExp.lastIndex = w.nextMatch;
		var match = this.inCellTermRegExp.exec(w.source);
		while(match) {
			if(match[1]) {
				//# nested table
				w.tableDepth++;
				w.subWikify(cell,this.tableTerm);
				w.nextMatch = this.tt;
				w.tableDepth--;
				return false;
			} else if(match[2]) {
				//# end table
				this.tt = this.inCellTermRegExp.lastIndex;
				return true;
			} else if(match[3]) {
				//# end row
				return false;
			} else if(match[4]) {
				//# cell
				var len = match[4].length;
				cell = createTiddlyElement2(e,match[4].substr(len-1)=='!'?'th':'td');
				//# skip over the match
				w.nextMatch += len;

				this.inCellTermRegExp.lastIndex = w.nextMatch;
				var lookahead = this.inCellTermRegExp.exec(w.source);
				if(!lookahead) {
					//# improperly terminated table
					return false;
				}
				var cellText = w.source.substr(w.nextMatch,lookahead.index-w.nextMatch);
				var oldSource = w.source;
				var i = MediaWikiFormatter.endOfParams(w,cellText);//cellText.indexOf('|');
				if(i!=-1) {
					cellText = cellText.replace(/^\+/mg,'');  //!!hack until I fix this properly
					MediaWikiFormatter.setAttributesFromParams(cell,cellText.substr(0,i-1));
					cellText = cellText.substring(i+1);
				}
				//# remove leading spaces so not treated as preformatted
				cellText = cellText.replace(/^\s*/mg,'');
				w.source = cellText;
				w.nextMatch = 0;
				w.subWikifyUnterm(cell);
				w.source = oldSource;
				w.nextMatch = lookahead.index;
			}
			this.inCellTermRegExp.lastIndex = w.nextMatch;
			match = this.inCellTermRegExp.exec(w.source);
		}//# end while
		return false;
	}//# end rowHandler
},

{
	name: 'mediaWikiList',
	match: '^[\\*#;:]+',
	lookaheadRegExp: /(?:(?:(\*)|(#)|(;)|(:))+)(?: ?)/mg,
	termRegExp: /(\n)/mg,
	handler: function(w)
	{
//#this.debug = createTiddlyElement2(w.output,'p');
//#mwDebug(this.debug,'start list');
		var stack = [w.output];
		var currLevel = 0, currType = null;
		var listType, itemType;
		w.nextMatch = w.matchStart;
		this.lookaheadRegExp.lastIndex = w.nextMatch;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
			if(lookaheadMatch[1]) {
				listType = 'ul';
				itemType = 'li';
			} else if(lookaheadMatch[2]) {
				listType = 'ol';
				itemType = 'li';
			} else if(lookaheadMatch[3]) {
				listType = 'dl';
				itemType = 'dt';
			} else if(lookaheadMatch[4]) {
				listType = 'dl';
				itemType = 'dd';
			}
			var listLevel = lookaheadMatch[0].length;
			w.nextMatch += listLevel;
			if(listLevel > currLevel) {
				for(var i=currLevel; i<listLevel; i++) {
					stack.push(createTiddlyElement2(stack[stack.length-1],listType));
				}
			} else if(listLevel < currLevel) {
				for(i=currLevel; i>listLevel; i--) {
					stack.pop();
				}
			} else if(listLevel == currLevel && listType != currType) {
				stack.pop();
				stack.push(createTiddlyElement2(stack[stack.length-1],listType));
			}
//#mwDebug(this.debug,"b:"+w.source.substr(w.nextMatch,30));
			currLevel = listLevel;
			currType = listType;
			var e = createTiddlyElement2(stack[stack.length-1],itemType);
			var ci = w.source.indexOf(':',w.nextMatch);
			var ni = w.source.indexOf('\n',w.nextMatch);
			if(itemType=='dt' && (ni==-1 || (ci!=-1 && ci<ni))) {
				//# deal with ':' on same line as ';'
				w.subWikifyTerm(e,/(:)/mg);
				w.nextMatch--;
			} else {
				w.subWikifyTerm(e,this.termRegExp);
			}
			this.lookaheadRegExp.lastIndex = w.nextMatch;
			lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		}
	}
},

{
	name: 'mediaWikiRule',
	match: '^----+$\\n?',
	handler: function(w)
	{
		//#var output = w.output.parentNode;
		createTiddlyElement2(w.output,'hr');
		//#w.output = createTiddlyElement2(output,'p');
	}
},

{
	name: 'mediaWikiLeadingSpaces',
	match: '^ ',
	lookaheadRegExp: /^ /mg,
	termRegExp: /(\n)/mg,
	handler: function(w)
	{
		var e = createTiddlyElement2(w.output,'pre');
		while(true) {
			w.subWikifyTerm(e,this.termRegExp);
			createTiddlyElement2(e,'br');
			this.lookaheadRegExp.lastIndex = w.nextMatch;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			if(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
				w.nextMatch += lookaheadMatch[0].length;
			} else {
				break;
			}
		}
	}
},

//# [[Image:Westminstpalace.jpg|frame|none|caption text]]
//# //http://en.wikipedia.org/wiki/Image:Westminstpalace.jpg
//# <a href="/wiki/Image:Westminstpalace.jpg" class="internal" title="caption text">
//# <img src="http://upload.wikimedia.org/wikipedia/commons/3/39/Westminstpalace.jpg"
//#  alt="caption text" width="400" height="300" longdesc="/wiki/Image:Westminstpalace.jpg" />
//# </a>

//# [[image:Stockholm.jpg|right|350px|thumb|Stockholm panorama from the City Hall]]
//# <div class="thumb tright">
//# 	<div style="width:352px;">
//# 		<a href="/wiki/Image:Stockholm.jpg" class="internal" title="Stockholm panorama from the City Hall">
//# 			<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Stockholm.jpg/350px-Stockholm.jpg" alt="Stockholm panorama from the City Hall" width="350" height="84" longdesc="/wiki/Image:Stockholm.jpg" />
//# 		</a>
//# 		<div class="thumbcaption">
//# 			<div class="magnify" style="float:right">
//# 				<a href="/wiki/Image:Stockholm.jpg" class="internal" title="Enlarge">
//# 				<img src="/skins-1.5/common/images/magnify-clip.png" width="15" height="11" alt="Enlarge" />
//# 				</a>
//# 			</div>
//# 			Stockholm panorama from the City Hall
//# 		</div>
//# 	</div>
//# </div>
{
	name: 'mediaWikiImage',
	match: '\\[\\[(?:[Ii]mage|Bild):',
	lookaheadRegExp: /\[\[(?:[Ii]mage|Bild):/mg,
	defaultPx: 180,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			var params = MediaWikiFormatter.getParams(w);
			var src = params[1];
			src = src.trim().replace(/ /mg,'_');
			src = src.substr(0,1).toUpperCase() + src.substring(1);
			var palign = null;
			var ptitle = null;
			var psrc = false;
			var px = null;
			var pthumb = false;
			var pframed = false;
			for(var i=2;i<params.length;i++) {
				//# right, left, center, none, sizepx, thumbnail (thumb), frame, and alternate (caption) text.
				var p = params[i];
				if(p=='right'||p=='left'||p=='center'||p=='none') {
					palign = p;
				} else if(p=='thumbnail'||p=='thumb') {
					pthumb = true;
				} else if(p=='framed') {
					pframed = true;
				} else if(/\d{1,4} ?px/.exec(p)) {
					px = p.substr(0,p.length-2).trim();
				} else {
					ptitle = p;
				}
			}//#end for
			if(pthumb) {
				//#var output = w.output.nodeType==1 && w.output.nodeName=='P' ? w.output.parentNode : w.output;
				var output = w.output;
				if(!palign) {
					palign = 'right';
				}
				if(!px) {
					px = 180;
				}
				psrc = px + 'px-' + src;
				var t = createTiddlyElement(output,'div',null,'thumb'+(palign?' t'+palign:''));
				var s = createTiddlyElement2(t,'div');
				s.style['width'] = Number(px) + 2 + 'px';
				var a = createTiddlyElement(s,'a',null,'internal');
				if(config.options.chkMediaWikiDisplayEnableThumbZoom) {
					a.href = src;
				}
				a.title = ptitle;
				var img = createTiddlyElement2(a,'img');
				img.src = 'images/' + psrc;
//#mwDebug(w.output,'s1:'+img.src);
				img.width = px;
				img.longdesc = 'Image:' + src;
				img.alt = ptitle;

				var tc = createTiddlyElement(s,'div',null,'thumbcaption');
				var oldSource = w.source; var oldMatch = w.nextMatch;
				w.source = ptitle; w.nextMatch = 0;
				w.subWikifyUnterm(tc);
				w.source = oldSource; w.nextMatch = oldMatch;

				if(config.options.chkMediaWikiDisplayEnableThumbZoom) {
					var tm = createTiddlyElement(tc,'div',null,'magnify');
					tm.style['float'] = 'right';
					var ta = createTiddlyElement(tm,'a',null,'internal');
					ta.title = 'Enlarge';
					timg = createTiddlyElement2(ta,'img'); timg.src = 'magnify-clip.png'; timg.alt = 'Enlarge'; timg.width = '15'; timg.height = '11';
					ta.href = src;
				}
			} else {
				//# not pthumb
				a = createTiddlyElement(w.output,'a',null,'image');
				a.title = ptitle;
				img = createTiddlyElement2(a,'img');
				if(palign) {img.align = palign;}
				img.src = px ? 'images/' + px + 'px-' + src : 'images/' + src;
//#mwDebug(w.output,'s2:'+img.src);
				if(px) {img.width = px;}
				img.longdesc = 'Image:' + src;
				img.alt = ptitle;
			}
		}
	}//#end image handler
},

{
	name: 'mediaWikiExplicitLink',
	match: '\\[\\[',
	lookaheadRegExp: /\[\[(?:([a-z]{2,3}:)?)(#?)([^\|\]]*?)(?:(\]\](\w*))|(\|(.*?)\]\]))/mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			if(!lookaheadMatch[1]) {
				//# not (eg) [[en:...]]
				var e;
				var link = lookaheadMatch[3];
				var text = link;
				//#var link2 = link;
				link = link.substr(0,1).toUpperCase() + link.substring(1);
				if(lookaheadMatch[4]) {
					//# Simple bracketted link
					if(lookaheadMatch[2]) {
						//# link to anchor
						var a = createTiddlyElement(w.output,'a');
						var t = w.tiddler ? MediaWikiFormatter.normalizedTitle(w.tiddler.title) + ':' : '';
						t = '#' + t + MediaWikiFormatter.normalizedTitle(link);
						a.setAttribute('href',t);
						a.title = '#' + MediaWikiFormatter.normalizedTitle(link);
						createTiddlyText(a,'#'+link);
					} else {
					//#mwDebug(w.output,'fm1:'+w.tiddler.title);
						e = createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
						if(lookaheadMatch[5]) {
							//# add any non-space after the ]]
							text += lookaheadMatch[5];
						}
						createTiddlyText(e,text);
					}
				} else if(lookaheadMatch[6]) {
					//# Piped link
					if(link.charAt(0)==':')
						link = link.substring(1);
					//#if(config.formatterHelpers.isExternalLink(link)) {
					//#	e = createExternalLink(w.output,link);
					//#} else {
					//#mwDebug(w.output,'fm2:'+w.tiddler.title);
						e = createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
					//#}
					var oldSource = w.source; var oldMatch = w.nextMatch;
					w.source = lookaheadMatch[7].trim(); w.nextMatch = 0;
					w.subWikifyUnterm(e);
					w.source = oldSource; w.nextMatch = oldMatch;
				}
			}
			w.nextMatch = this.lookaheadRegExp.lastIndex;
		}
	}
},

//#{{Audio|sv-Stockholm.ogg|Stockholm}}
{
	name: 'mediaWikiTemplate',
	match: '\\{\\{[^\\{]',
	lookaheadRegExp: /\{\{((?:.|\n)*?)\}\}/mg,
	handler: function(w)
	{
//# mwDebug(w.output,'wt:'+w.matchText+' ws:'+w.matchStart+' wn:'+w.nextMatch+' wl:'+w.matchLength);
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
//# mwDebug(w.output,'lm:'+lookaheadMatch);
//# mwDebug(w.output,'lmi:'+lookaheadMatch.index+' lI:'+this.lookaheadRegExp.lastIndex);
//# mwDebug(w.output,'lm1:'+lookaheadMatch[1]);
//# mwDebug(w.output,'lm2:'+lookaheadMatch[2]);
			var lastIndex = this.lookaheadRegExp.lastIndex;
			var contents = lookaheadMatch[1];
			if(MediaWikiFormatter.expandVariable(w,contents)) {
				w.nextMatch = lastIndex;
				return;
			}
			var i = contents.indexOf('|');
			var title = i==-1 ? contents : contents.substr(0,i);
			//# normalize title
			title = title.trim().replace(/_/mg,' ');
			title = 'Template:' + title.substr(0,1).toUpperCase() + title.substring(1);
			var tiddler = store.fetchTiddler(title);
			var oldSource = w.source;
			if(tiddler) {
				params = {};
				if(i!=-1) {
					//#w.nextMatch = 0;
					params = MediaWikiFormatter.getTemplateParams(lookaheadMatch[1]);
				}
				w.source = MediaWikiFormatter.expandTemplate(w,tiddler.text,params);
				w.nextMatch = 0;
				w.subWikifyUnterm(w.output);
			} else {
				if(config.options.chkMediaWikiDisplayEmptyTemplateLinks) {
					//# for conveniece, output the name of the template so can click on it and create tiddler
					w.source = '[['+title+']]';
					w.nextMatch = 0;
					w.subWikifyUnterm(w.output);
				}
			}
			w.source = oldSource;
			w.nextMatch = lastIndex;
		}
	}
},

{
	name: 'mediaWikiParagraph',
	match: '\\n{2,}',
	handler: function(w)
	{
		//#var output = w.output.nodeType==1 && w.output.nodeName=='P' ? w.output.parentNode : w.output;
		w.output = createTiddlyElement2(w.output,'p');
	}
},

{
	name: 'mediaWikiExplicitLineBreak',
	match: '<br ?/?>',
	handler: function(w)
	{
		createTiddlyElement2(w.output,'br');
	}
},

{
	name: 'mediaWikiExplicitLineBreakWithParams',
	match: "<br(?:\\s*(?:(?:.*?)=[\"']?(?:.*?)[\"']?))*?\\s*/?>",
	lookaheadRegExp: /<br((?:\s+(?:.*?)=["']?(?:.*?)["']?)*?)?\s*\/?>/mg,
	handler: function(w)
	{
		//# copes with erroneous <br clear='right'>
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			var e =createTiddlyElement2(w.output,'br');
			if(lookaheadMatch[1]) {
				MediaWikiFormatter.setAttributesFromParams(e,lookaheadMatch[1]);
			}
			w.nextMatch = this.lookaheadRegExp.lastIndex;// empty tag
		}
	}
},

{
	name: 'mediaWikiTitledUrlLink',
	match: '\\[' + config.textPrimitives.urlPattern + '(?:\\s+[^\\]]+)?' + '\\]',
	//# eg [http://www.nupedia.com] or [http://www.nupedia.com Nupedia]
	//# <sup id='_ref-1' class='reference'><a href='#_note-1' title=''>[2]</a>
	handler: function(w)
	{
		var lookaheadRegExp = new RegExp('\\[(' + config.textPrimitives.urlPattern + ')(?:\\s+([^\[]+))?' + '\\]','mg');
		lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index==w.matchStart) {
			var link = lookaheadMatch[1];
			if(lookaheadMatch[2]) {
				var e = createExternalLink(w.output,link);
				var oldSource = w.source; var oldMatch = w.nextMatch;
				w.source = lookaheadMatch[2].trim(); w.nextMatch = 0;
				w.subWikifyUnterm(e);
				w.source = oldSource; w.nextMatch = oldMatch;
			} else {
				e = createExternalLink(createTiddlyElement2(w.output,'sup'),link);
				w.linkCount++;
				createTiddlyText(e,'['+w.linkCount+']');
			}
			w.nextMatch = lookaheadRegExp.lastIndex;
		}
	}
},

{
	name: 'mediaWikiUrlLink',
	match: config.textPrimitives.urlPattern,
	handler: function(w)
	{
		w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
	}
},

{
	name: 'mediaWikiBoldItalic',
	match: "'''''",
	termRegExp: /('''''|(?=\n))/mg,
	element: 'strong',
	handler: function(w)
	{
		var e = createTiddlyElement(w.output,this.element);
		w.subWikifyTerm(createTiddlyElement(e,'em'),this.termRegExp);
	}
},

{
	name: 'mediaWikiBold',
	match: "'''",
	termRegExp: /('''|(?=\n))/mg,
	element: 'strong',
	handler: config.formatterHelpers.createElementAndWikify
},

{
	name: 'mediaWikiItalic',
	match: "''",
	termRegExp: /((?:''(?!'))|(?=\n))/mg,
	element: 'em',
	handler: config.formatterHelpers.createElementAndWikify
},

{
	name: 'mediaWikiUnderline',
	match: '<u>',
	termRegExp: /(<\/u>|(?=\n))/mg,
	element: 'u',
	handler: config.formatterHelpers.createElementAndWikify
},

{
	name: 'mediaWikiStrike',
	match: '<s>',
	termRegExp: /(<\/s>|(?=\n))/mg,
	element: 'strike',
	handler: config.formatterHelpers.createElementAndWikify
},

{
	name: 'mediaWikiBoldTag',
	match: '<b>',
	termRegExp: /(<\/b>|(?=\n))/mg,
	element: 'b',
	handler: config.formatterHelpers.createElementAndWikify
},

{
	name: 'mediaWikiItalicTag',
	match: '<i>',
	termRegExp: /(<\/i>|(?=\n))/mg,
	element: 'i',
	handler: config.formatterHelpers.createElementAndWikify
},

{
	//# note, this only gets invoked when viewing the template
	name: 'mediaWikiTemplateParam',
	match: '\\{\\{\\{',
	lookaheadRegExp: /(\{\{\{(?:.|\n)*?\}\}\})/mg,
	element: 'span',
	handler: config.formatterHelpers.enclosedTextHelper
},

//# See http://en.wikipedia.org/wiki/Wikipedia:Footnotes
//# for an explanation of how to generate footnotes using the <ref(erences/)> tags
{
	name: 'mediaWikiInsertReference',
	match: '<ref[^/]*>',
	lookaheadRegExp: /<ref(\s+(?:.*?)=["']?(?:.*?)["']?)?>([^<]*?)<\/ref>/mg,
	//#lookaheadRegExp: /<ref(\s+(?:.*?)=["']?(?:.*?)["']?)?>([.\n]*?)<\/ref>/mg,
	handler: function(w)
	{
		if(config.browser.isIE) {
			refRegExp = /<ref[^\/]*>((?:.|\n)*?)<\/ref>/mg;
			refRegExp.lastIndex = w.matchStart;
			var refMatch = refRegExp.exec(w.source);
			if(refMatch && refMatch.index == w.matchStart) {
				w.nextMatch = refRegExp.lastIndex;
				return;
			}
		}
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			var x = {id:'',value:''};
			w.nextMatch = this.lookaheadRegExp.lastIndex;
			if(!w.referenceCount) {
				w.referenceCount = 0;
				w.references = {};
			}
			var s = createTiddlyElement(w.output,'sup',null,'reference');
			var a = createTiddlyElement2(s,'a');
			var prefix = w.tiddler ? w.tiddler.title + ':' : '';
			var name;
			if(lookaheadMatch[1]) {
				//# <ref params>
				//#var r = {};
				var r = MediaWikiFormatter.setFromParams(w,lookaheadMatch[1]);
				name = r.name ? r.name.trim() : '';
				name = name.replace(/ /g,'_');
				s.id = prefix + '_ref-' + name;// + '_' + nameCount;(w.referenceCount+1);
				if(!w.references[name]) {
					w.references[name] = x;
					w.references[name].id = w.referenceCount;
					w.references[name].value = lookaheadMatch[2].trim();
				}
			} else {
				//# <ref>, repeat reference
				w.references[w.referenceCount] = x;
				w.references[w.referenceCount].id = w.referenceCount;
				w.references[w.referenceCount].value = lookaheadMatch[2].trim();
				name = w.referenceCount;
				s.id = prefix + '_ref-' + w.referenceCount;
			}
			w.referenceCount++;
			a.title = lookaheadMatch[2].trim();//mb, extra to wikipedia
			a.href = '#' + prefix + '_note-' + name;
			a.innerHTML = '['+w.referenceCount+']';
//#<sup id='_ref-0' class='reference'><a href='#_note-0' title=''>[1]</a></sup>
//#<sup id='_ref-foreign_ministry_0' class='reference'><a href='#_note-foreign_ministry' title=''>[2]</a></sup>
		}
	}
},

{
	name: 'mediaWikiListReferences',
	match: '<references ?/>',
	lookaheadRegExp: /<references ?\/>/mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(config.options.chkMediaWikiListReferences && w.referenceCount) {
			var ol = createTiddlyElement(w.output,'ol',null,'references');
			var oldSource = w.source;
			if(w.referenceCount>0) {
				for(var i in w.references) {
					var li = createTiddlyElement2(ol,'li');
					var prefix = w.tiddler ? w.tiddler.title + ':' : '';
					var b = createTiddlyElement2(li,'b');
					var a = createTiddlyElement2(b,'a');
					li.id = prefix + '_note-' + i;
					a.href = '#' + prefix + '_ref-' + i;
					a.innerHTML = '^';
					w.source = w.references[i].value;
					w.nextMatch = 0;
					w.subWikifyUnterm(li);
				}
			}
			w.source = oldSource;
		}
		w.nextMatch = this.lookaheadRegExp.lastIndex;
	}
},

{
	name: 'mediaWikiRepeatReference',
	match: '<ref[^/]*/>',
	lookaheadRegExp: /<ref(\s+(?:.*?)=["'](?:.*?)["'])?\s*\/>/mg,
	handler: function(w)
	{
		if(config.browser.isIE) {
			refRegExp = /<ref.*?\/>/mg;
			refRegExp.lastIndex = w.matchStart;
			var refMatch = refRegExp.exec(w.source);
			if(refMatch && refMatch.index == w.matchStart) {
				w.nextMatch = refRegExp.lastIndex;
				return;
			}
		}
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			var x = {id:'',value:''};
			w.nextMatch = this.lookaheadRegExp.lastIndex;
//#<ref name="foreign ministry">
//#<sup id="_ref-foreign_ministry_1" class="reference"><a href="#_note-foreign_ministry" title="">[2]</a></sup>
			var s = createTiddlyElement(w.output,"sup",null,"reference");
			var a = createTiddlyElement2(s,"a");
			var prefix = w.tiddler ? w.tiddler.title : '';
			if(lookaheadMatch[1]) {
				var r = {};
				r = MediaWikiFormatter.setFromParams(w,lookaheadMatch[1]);
				var name = r.name ? r.name.trim() : '';
				name = name.replace(/ /g,'_');
				s.id = prefix + '_ref-' + name +'_' + (w.referenceCount+1);
				var count = w.references && w.references[name] ? (w.references[name].id+1) : '?';
			}
			a.href = '#' + prefix + '_note-' + name;
			a.innerHTML = '['+count+']';
			a.title = name;
		}
	}//# end handler
},

{
	name: 'mediaWikiHtmlEntitiesEncoding',
	match: '&#?[a-zA-Z0-9]{2,8};',
	handler: function(w)
	{
		if(!config.browser.isIE)
			createTiddlyElement(w.output,"span").innerHTML = w.matchText;
	}
},

{
	name: 'mediaWikiComment',
	match: '<!\\-\\-',
	lookaheadRegExp: /<!\-\-((?:.|\n)*?)\-\->/mg,
	//#lookaheadRegExp: /<!\-\-([.\n]*?)\-\->/mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			w.nextMatch = this.lookaheadRegExp.lastIndex;
		}
	}
},

{
	name: 'mediaWikiIncludeOnly',
	match: '<includeonly>',
	lookaheadRegExp: /<includeonly>((?:.|\n)*?)<\/includeonly>/mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			w.nextMatch = this.lookaheadRegExp.lastIndex;
		}
	}
},

{
	name: 'mediaWikiNoWiki',
	match: '<nowiki>',
	lookaheadRegExp: /<nowiki>((?:.|\n)*?)<\/nowiki>/mg,
	element: 'span',
	handler: config.formatterHelpers.enclosedTextHelper
},

{
	name: 'mediaWikiPreNoWiki',
	match: '<pre>\s*<nowiki>',
	lookaheadRegExp: /<pre>\s*<nowiki>((?:.|\n)*?)<\/nowiki>\s*<\/pre>/mg,
	element: 'pre',
	handler: config.formatterHelpers.enclosedTextHelper
},

{
	name: 'mediaWikiPre',
	match: '<pre>',
	lookaheadRegExp: /<pre>((?:.|\n)*?)<\/pre>/mg,
	element: 'pre',
	handler: config.formatterHelpers.enclosedTextHelper
},

{
	name: 'mediaWikiMagicWords',
	match: '__',
	lookaheadRegExp: /__([A-Z]*?)__/mg,
	//# see http://meta.wikimedia.org/wiki/Help:Magic_words
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			//# deal with variables by name here
			if(lookaheadMatch[1]=='NOTOC') {
				//# do nothing
			} else if(config.options.chkDisplayMediaWikiMagicWords) {
				//# just output the text of any variables that are not understood
				w.outputText(w.output,w.matchStart,w.nextMatch);
			}
			w.nextMatch = this.lookaheadRegExp.lastIndex;
		}
	}
},

{
	name: 'mediaWikiGallery',
	match: '<gallery>',
	lookaheadRegExp: /[Ii]mage:(.*?)\n/mg,
	handler: function(w)
	{
//#basic syntax is:
//#<gallery>
//#Image:Wiki.png
//#Image:Wiki.png|Captioned
//#Image:Wiki.png|[[Help:Contents/Links|Links]] can be put in captions.
//#Image:Wiki.png|Full [[MediaWiki]]<br />[[syntax]] may now be used�
//#</gallery>
//#<table class="gallery" cellspacing="0" cellpadding="0">
//#<tr>
//#...
//#</tr>
//#</table>
		var table = createTiddlyElement(w.output,'table',null,'gallery');
		table.cellspacing = '0';
		table.cellpadding = '0';
		var rowElem = createTiddlyElement2(table,'tr');
		var col = 0;
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var nM = w.nextMatch;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		var oldSource = w.source;
		while(lookaheadMatch) {
			nM += lookaheadMatch[1].length;
			w.source = lookaheadMatch[1] +']]';//!! ]] is hack until getParams is working
			w.nextMatch = 0;
			var params = MediaWikiFormatter.getParams(w);
			var src = params[1];
			src = src.trim().replace(/ /mg,'_');
			src = src.substr(0,1).toUpperCase() + src.substring(1);
			var palign = 'right'; 
			var psrc = '120px-'+src;
			var px = 120;
			var pframed = false;
			ptitle = null;
			for(var i=2;i<params.length;i++) {
				//# right, left, center, none, sizepx, thumbnail (thumb), frame, and alternate (caption) text.
				var p = params[i];
				if(p=='right'||p=='left'||p=='center'||p=='none') {
					palign = p;
				} else if(p=='framed') {
					pframed = true;
				} else if(/\d{1,4}px/.exec(p)) {
					px = p.substr(0,p.length-2).trim();
					psrc = px + 'px-' + src;
				} else {
					ptitle = p;
				}
			}//#end for
//#<td>
//#<div class="gallerybox">
//#	<div class="thumb" style="padding: 26px 0;">
//#		<a href="/wiki/Image:Paul_C%C3%A9zanne_184.jpg" title="Image:Paul C�zanne 184.jpg">
//#		<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/6/60/Paul_C%C3%A9zanne_184.jpg/120px-Paul_C%C3%A9zanne_184.jpg" width="120" height="94" alt="" />
//#		</a>
//#	</div>
//#	<div class="gallerytext">
//#		<p><i>La Pain et les Oeufs</i> (Bread and Eggs), thought to present austerity, 1865. Signed and dated. Possibly in Spanish style.</p>
//#	</div>
//#</div>
//#</td>
			var td = createTiddlyElement2(rowElem,'td');
			var gb = createTiddlyElement(td,'div',null,'gallerybox');
			var t = createTiddlyElement(gb,'div',null,'thumb');
			t.style['padding'] = '26px 0';

			var a = createTiddlyElement2(t,'a');
			if(config.options.chkMediaWikiDisplayEnableThumbZoom) {
				a.href = src;
			}
			a.title = ptitle;
			var img = createTiddlyElement2(a,'img');
			img.src = psrc;
			img.width = px;
			//#ptitle;
			img.alt = '';

			var gt = createTiddlyElement(gb,'div',null,'gallerytext');
			p = createTiddlyElement2(gt,'p');
			var oldSource2 = w.source; var oldMatch = w.nextMatch;
			w.source = ptitle; w.nextMatch = 0;
			w.subWikifyUnterm(p);
			w.source = oldSource2; w.nextMatch = oldMatch;

			col++;
			if(col>3) {
				rowElem = createTiddlyElement2(table,'tr');
				col = 0;
			}
			w.source = oldSource;
			lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		}
		w.nextMatch = nM + '<gallery>'.length*2+1+'Image:'.length;//!! hack
	}
},

{
	name: 'mediaWikiHtmlTag',
	match: "<[a-zA-Z]{2,}(?:\\s*(?:(?:.*?)=[\"']?(?:.*?)[\"']?))*?>",
	lookaheadRegExp: /<([a-zA-Z]{2,})((?:\s+(?:.*?)=["']?(?:.*?)["']?)*?)?\s*(\/)?>/mg,
	handler: function(w)
	{
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
			var e =createTiddlyElement2(w.output,lookaheadMatch[1]);
			if(lookaheadMatch[2]) {
				MediaWikiFormatter.setAttributesFromParams(e,lookaheadMatch[2]);
			}
			if(lookaheadMatch[3]) {
				//# empty tag
				w.nextMatch = this.lookaheadRegExp.lastIndex;
			} else {
				w.subWikify(e,'</'+lookaheadMatch[1]+'>');
			}
		}
	}
}
];

config.parsers.mediawikiFormatter = new Formatter(config.mediawiki.formatters);
config.parsers.mediawikiFormatter.format = 'mediawiki';
config.parsers.mediawikiFormatter.formatTag = 'MediaWikiFormat';
} //# end of 'install only once'
//}}}
A single installation of the '''Habari''' source code can power multiple, independent sites.

If you have only a single site, then '''Habari''' will use the config.php file that resides in the same directory as index.php.  This is the default configuration file, and will be used if no site-specific configuration can be found.

In order to run multiple sites, you will first need to create a <code>sites</code> directory inside your <code>user</code> directory.  Then, inside <code>user/sites</code> you will create a directory for each site you want to run.  Inside each of these sub-directories you will place a config.php with that site's configuration directives.

The name of the directory to use for each site is constructed in the following way:
* strip off the protocol used to access the site. http://habariproject.org/en/ becomes habariproject.org/en/
* replace all slashes with periods. So habariproject.org/en becomes habariproject.org.en.
* place any non-standard HTTP port at the beginning of the directory. So habariproject.org/en:8080 becomes 8080.habariproject.org.en

'''Habari''' will traverse the directories in /user/sites looking for the closest match.  So, for a request to www.habariproject.org/en, '''Habari''' will look in the following directories in the following order for a config.php
* www.habariproject.org.en
* habariproject.org.en
* org.en
* www.habariproject.org
* habariproject.org
* org

Additional '''Habari'''-powered sites do not need to use the same domain.  Provided the web server is properly configured, a single installation of the '''Habari''' source code can serve www.habariproject.org, www.habarithemes.org, and www.habari-ya-kazi.com.  Likewise, sub-domains and sub-directories are also supported.

Each '''Habari''' site can use its own themes and plugins, as well as the themes and plugins available to the "main" site.  Simply create /themes and /plugins sub-directories inside each site's directory.  A theme or plugin located in a site's directory structure is only available to that site; while themes and plugins in the top-level /user directory will be available to all sites.

== Notes ==
# Each '''Habari''' site is strictly independent.  Although sites may share themes and plugins located in the top-level /user directory, no data is shared between sites.  User accounts, posts, and settings are completely independent.
# In order for the installation routine to work for a site other than the default site, there '''must''' be a <code>config.php</code> in that site's directory.
A single installation of the '''Habari''' source code can power multiple, independent sites.

If you have only a single site, then '''Habari''' will use the config.php file that resides in the same directory as index.php.  This is the default configuration file, and will be used if no site-specific configuration can be found.

In order to run multiple sites, you will first need to create a <code>sites</code> directory inside your <code>user</code> directory.  Then, inside <code>user/sites</code> you will create a directory for each site you want to run.  Inside each of these sub-directories you will place a config.php with that site's configuration directives.

The name of the directory to use for each site is constructed in the following way:
* strip off the protocol used to access the site. http://habariproject.org/en/ becomes habariproject.org/en/
* replace all slashes with periods. So habariproject.org/en becomes habariproject.org.en.
* place any non-standard HTTP port at the beginning of the directory. So habariproject.org/en:8080 becomes 8080.habariproject.org.en

'''Habari''' will traverse the directories in /user/sites looking for the closest match.  So, for a request to www.habariproject.org/en, '''Habari''' will look in the following directories in the following order for a config.php
* www.habariproject.org.en
* habariproject.org.en
* org.en
* www.habariproject.org
* habariproject.org
* org

Additional '''Habari'''-powered sites do not need to use the same domain.  Provided the web server is properly configured, a single installation of the '''Habari''' source code can serve www.habariproject.org, www.habarithemes.org, and www.habari-ya-kazi.com.  Likewise, sub-domains and sub-directories are also supported.

Each '''Habari''' site can use its own themes and plugins, as well as the themes and plugins available to the "main" site.  Simply create /themes and /plugins sub-directories inside each site's directory.  A theme or plugin located in a site's directory structure is only available to that site; while themes and plugins in the top-level /user directory will be available to all sites.

== Notes ==
# Each '''Habari''' site is strictly independent.  Although sites may share themes and plugins located in the top-level /user directory, no data is shared between sites.  User accounts, posts, and settings are completely independent.
# In order for the installation routine to work for a site other than the default site, there '''must''' be a <code>config.php</code> in that site's directory.
<!--{{{-->

<div id='topMenu' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
As of revision 756 Habari now supports the sending and recieving of Pingbacks. Displaying them on your site is simple. Let's go over the code you will need to add to your comments template.

By default Habari grabs all the comments for a given post (normal comments, trackbacks, etc) and sorts them into "buckets" for later use.  For instance when we want to display all our comments, we would do something like this:
<source lang="php">
if ( $post->comments->approved->count ) {
  foreach ( $post->comments->comments->approved as $comment ) { 
</source>

The code above checks if there are approved comments for this post, and if so begins a loop to process them for display. The code for displaying pingbacks is much the same:

<source lang="php">
if ( $post->comments->pingbacks->count ) {
  foreach ( $post->comments->pingbacks->approved as $pingback ) {
</source>

If there are approved pingbacks for this post they are processed for display.  Here is complete sample code to display pingbacks:

<source lang="php">
<?php if ( $post->comments->pingbacks->count ) : ?>
  <h1>
  <?php 
    echo $post->comments->pingbacks->count . ' ';
    echo _n( 'Pingback', 'Pingbacks', $post->comments->pingbacks->count );
    echo ' ' . to ' ' . $post->title; ?>
  </h1>
  <div id="pings">
    <ol id="pings-list">
      <?php foreach ( $post->comments->pingbacks->approved as $pingback ) : ?>
        <li id="ping-<?php echo $pingback->id; ?>">
          <p>
            <?php
              echo "<a href=\"{$pingback->url}\">{$pingback->name}</a> &raquo; {$pingback->content}";
            ?>
          </p>
        </li>
      <?php endforeach; ?>
    </ol>
  </div>
<?php endif; ?>
</source>

The default comments template in the k2 theme will show both pingbacks and comments. To show only comments use the following code.

<source lang="php">$post->comments->pingbacks->approved->count;</source>

[[Category:Manual]]
Plugins are intended to be extensions of the core functionality of Habari.  Your installation will come with several, community developed plugins, or you may download other, community and individually developed plugins.  Eventually, a repository on the official site will house freely available plugins, until then, you can check out the [[Available Plugins]].

==Installation & Activation==

The directory structure must be kept in place, that is, do not upload just the php files of the plugin, rather, the entire directory, even if only one file exists.

===System Plugins vs User Plugins===
Core plugins are installed in <tt>/system/plugins</tt>.  User installed plugins should be installed in <tt>/user/plugins/</tt>.  This system allows for upgrades, without having to touch the <tt>/user</tt> directory at all.  If a plugin exists in both locations, the <tt>/user/plugins</tt> will override the system version. Note, all of the files and directories of a plugin must live in the <tt>/user/plugin</tt> directory, it will not pull from both. 

To activate and configure your plugins navigate your browser to <tt>/admin/plugins</tt>, and a list of all plugins in your user directory should be available. To active or deactivate a plugin, simply click the button to the right of the plugin name.  Some plugins also provide configuration settings, clicking the configure link will open the options below the list of your installed plugins (but note that the interface for plugin configuration is still in development). Note that a "sandboxing" function exists to check a plugins syntax before being activated.  If any errors exist, the plugin will not be available for activation.  If this occurs, you should contact the plugin author, or check for a newer version.

==Enabling the Plugin==
Some features provided by plugins will be enabled without you doing anything other than activating the plugin.  However, some plugins will require adding code to your active theme to output some data.  Generally speaking, the plugin author should provide documentation for this, however, in most cases the plugin sets a variable and adds it to the $theme instance, which represents the active theme. Within the template where you want the plugin's data displayed, you simply echo that variable.  This is done so that in the future, when other theme engines are added, specific PHP code isn't required for the plugin to work with that theme engine.  If a plugin sets the variable <code>$my_twitter</code>, in the raw PHP engine you would add <code><?php echo $my_twitter; ?></code> to the template where you want to show the data.  As other engines are added, this will be updated to reflect those as well.

==Checking for Plugins in Themes==
You can use a conditional statement to see if a particular plugin is active, and if so, do something, else, do something else.  

This is '''not recommended''' for most purposes, especially in template files, because [[Theme Functions]] provide more functionality and have the plugin detection built-in, and [[Theme Dependencies]] allow you to specify Theme Functions and versions that must be present for your theme to work properly before it's even activated.

To test if a plugin is present, use the following function:

<code lang="php"><?php Plugins::is_loaded(); ?></code>

You need to pass at least one variable to the function, the <code>$name</code>.  <code>$name</code> can either be from the plugin info, or the class.  You can also pass a version number, however this is optional.  So for example, if you want to check to see if the Pingback plugin is active, and is at least version 1.0, you would do something to the effect of:
<code lang="php"><?php if (Plugins::is_loaded('pingback','1.0')) {
  //do something
}
else {
  //something instead
}

?></code>

However, if you do not need to test against a version, you simply need :
<code lang="php"><?php if (Plugins::is_loaded('pingback')) {
 //do something
}
else {
  //something instead
}

?></code>

[[Category:Manual]]
A single instance of QueryRecord represents a single query result row.  QueryRecord also serves as the base class for derivative classes that provide row-level data access.  QueryRecord is, in many ways, the fundamental building block of Habari.

==Using QueryRecord==

QueryRecord provides basic functions that allow Habari to interact with the single row of data.  The properties of a QueryRecord object represent the field values of the row.  

To obtain an instance of QueryRecord with data, call one of these static methods on the DB class:
; get_row():Returns a single QueryRecord instance for the single row retrieved
; get_results():Returns an array of QueryRecord instances, one per row returned in the query

For example:
<pre>$record = DB::get_row('SELECT id, bar FROM foo WHERE id = 1');
 echo $record->bar;</pre>

The code above firsts queries the database for a record containing the fields <tt>id</tt> and <tt>bar</tt> from the table 'foo' where the 'id' field is equal to 1.  The single record (only one is returned by <tt>DB::get_row()</tt>) is stored in <tt>$record</tt> as an instance of the QueryRecord class.  The property <tt>bar</tt> referenced as <tt>$record->bar</tt> contains the value of the 'bar' field returned by the query.  Similarly, although this is not shown, the 'id' field is assigned to <tt>$record->id</tt>.

By echoing the value of <tt>$record->bar</tt>, the value of that field is output.

==Updating a Record==

By changing the values of these properties and calling the update() method on the object, the QueryRow can commit that data back to the database.  For example:

<pre>$record = DB::get_row('SELECT id, bar FROM foo WHERE id = 1');
 $record->bar = 'hello';
 $record->update('foo', array('id'));</pre>

As with the prior example, the values of the fields 'id' and 'bar' have been queried into an instance of QueryRecord.

The <tt>$record->bar</tt> is then assigned the value 'hello'.  This prepares the new data for update in the database.  The value of <tt>$record->bar</tt> will contain 'hello' after this assignment.  

To update the record in the database, the <tt>update()</tt> method is called.  In QueryRecord, update() accepts two parameters.  The first is the table to which the data will be written and the second is an array of fields that will be used to match the data in the object to the correct row in the database.

In this case, the table updated is 'foo', and the value of <tt>$record->id</tt> will be matched against the field 'id' in the database.  Any record in the database with a matching id value will have its fields updated to the other property values of <tt>$record</tt>

==Inserting a Record==

QueryRecord can also be used to insert new records into a table.  For example:

<pre>$record = new QueryRecord(
   array('bar'=>'hello')
 );
 $record->insert('foo');</pre>

In the above example, a new instance of QueryRecord is created and asigned to <tt>$record</tt>.  The values of the properties in the new QueryRecord are built from the associative array that is passed to the QueryRecord constructor.  The keys of the array become the object's properties, and the values of the array become those properties' values.  After creating <tt>$record</tt> as above, the value of <tt>$record->bar</tt> would be set to 'hello'.

Calling <tt>$record->insert()</tt> inserts the property values into the specified table, in this case, 'foo'.

In order for this to work correctly, all of the values required to insert a row into a table must have been set as properties of the QueryRecord object.  Note that in this example, the 'id' field is omitted with the assumption that it is an auto_increment primary key in the 'foo' table.

After the row is inserted, the value of 'id' *will not* be set in <tt>$record->id</tt>.  This must be done manually using <tt>DB::last_insert_id()</tt>, which is often handled from a subclass, as described in the Extending QueryRecord section below.

==Deleting a Record==

This is fundamentally the same as updating a row, except it deletes the row from the table that has fields with the matching values.

<pre>$record->delete('foo', array('id'));</pre>

==Extending QueryRecord==

As part of Habari's Object-Oriented approach, other classes can extend the QueryRecord class to provide additional methods that interact with QueryRecords that contain a specific type of data.  For example, the Post class extends QueryRecord to provide additional methods that are post-specific.  The class that extends QueryRecord [http://en.wikipedia.org/wiki/Inheritance_%28computer_science%29 inherits] all of the methods of QueryRecord in addition to any that it provides on its own.

In most cases, a QueryRecord is not the class of a row.  A row is usually requested as one of QueryRecord's descendant classes.

Many classes extend the QueryRecord class.  The following classes all inherit from QueryRecord:
; Post: Represents a single post
; Comment: Represents a single comment
; LogEntry: Represents a single entry in the event log
; CronJob: Represents a single periodically executed task
; RewriteRule: Represents a single rewrite rule
; User: Represents a single user

The classes above represent wildly different functionality and and data representations; yet they each extend the base QueryRecord class.  By providing this core functionality for descendant classes, extended classes don't need to duplicate code.  

QueryRecord descendants map to database tables of the same name, and most of the properties of the descendants map the columns of that table.  For example, the Post table is used to store Post object data.  The Post object has fields 'id', 'title', and 'content', among others.  These fields are also columns in the 'posts' table in the database.

The Post class also provides special functionality that is associated only to posts.  For example, every post has a status value.  In the database, this status value is stored as an integer.  Because it's often easier to work with the names of these status, the Post class provides the ability to accept either the integer or the string as the value of the poperty, but always stores the value in the database as an integer.  This is just one example of what additional functionality a subclass of QueryRecord can provide.

One of the more significant additions a subclass can make is to override the <tt>update()</tt> and <tt>insert()</tt> methods of QueryRecord.  By creating having its own <tt>update()</tt> method, a class can identify the correct table and key fields that are used to update that row in the database.  This allows a subclass instance to omit the table and keyfields parameters in the call to update.  So when updating a post, instead of this:

<pre>$record->update('foo', array('id'));</pre>

You would simply call this:

<pre>$post->update();</pre>

Using a subclass, you can transparently assign the value of DB::last_insert_id() to the keyfield property of the object.  This will allow you to update the object after it is inserted without having to manually retrieve the key values each time.  So when inserting a post, instead of this, where the <tt>id</tt> property must be set manually:

<pre>$record = new QueryRecord(/* post field data */);
 $record->insert('posts');
 $record->id = DB::last_insert_id();
 $record->title = 'new title';
 $record->update('post', array('id'));</pre>

You would simply call this:

<pre>$post = new Post(/* post field data*/);
 $post->insert();
 $post->title = 'new title';
 $post->update();</pre>

It's not that the assignment doesn't need to be made at all, but that it takes place in the Post class (which extends QueryRecord) so that you need not repeat it whenever you insert a new post.

Please review the documentation for the classes that extend QueryRecord for the details of functionality that they provide beyond QueryRecord itself.


QueryRecord provides the following:
* [http://us.php.net/manual/en/language.oop5.overloading.php overloaded] __get(), __set() and __isset()
* exclude_fields() and list_excluded_fields(), for identifying properties handled exclusively by the database
* insert(), for adding data to the database
* to_array(), to permit array operations on the object (like <code>foreach</code>)
* get_url_args(), which returns an array of the values represented by the object
* update(), for recording modified values to the database
* delete(), for deleting an item from the database
It's really simple to get a listing of recent comments to output in your Habari theme.  It's just two steps.

First, add this line to your custom <tt>theme.php</tt> file for your theme inside the <tt>add_template_vars</tt> function:

<pre>$this->assign('recent_comments', Comments::get( array('limit'=>25, 'status'=>Comment::STATUS_APPROVED, 'orderby'=>'date DESC' ) ) );</pre>

If you are using the Pingback plugin, and want to remove those from your recent comments, you can use:
<pre>$this->assign('recent_comments', Comments::get( array('limit'=>25, 'status'=>Comment::STATUS_APPROVED, 'type'=>Comment::COMMENT, 'orderby'=>'date DESC' ) ) );</pre>

note the addition of 'type'.  You could in theory separately list 5 most recent pingbacks with a modification of code.

This tells Habari to store the 25 most recent approved comments into a variable called $recent_comments that will be accessible in your template.

Second, add this to your theme's template where you want the comment listing to appear:

<pre><h3>Recent Comments</h3>
<ul>
<?php foreach($recent_comments as $comment): ?>
<li><a href="<?php echo $comment->url ?>"><?php echo $comment->name ?></a> on <a href="<?php echo $comment->post->permalink; ?>"><?php echo $comment->post->title; ?></a></li>
<?php endforeach; ?>
</ul></pre>

Note that there's a bunch of HTML in there that you can change without affecting the basic output of comments.

This code displays the HTML between the <code>foreach</code> command and the <code>endforeach</code> command once for each recent comment.  The comment data is returned relative to the <code>$commment</code> shown in the <code>foreach</code> command.

* <code>$comment->name</code> is the name of the commenter.
* <code>$comment->url</code> is the URL of the commenter.
* <code>$comment->post</code> is the post object on which the commenter commented.
* <code>$comment->post->title</code> is the title of the post to which the comment applies.

You could even use <code>$comment->content</code> to output the content of the comment, if you wanted.


==Parts of a rewrite rule==
Rewrite rules are made up of several parts conforming to the fields in
the rewrite_rules table, all with a distinct purpose.

'''name''' - The name of the rule.  Used in the code to reference the rule
to create links.

'''parse_regex''' - A regular expression that attempts to match an incoming
request.  If it does, then the rule is used to determine what to
display.

'''build_str''' - A string used to build a URL from code that will be
matched by the parse_regex.

'''handler''' - The Habari class that handles the request.

'''action''' - The action ("function") in the handler class that handles the request.

'''priority''' - Rules are tested in order from low to high.  Matching rules
short-circuit the testing process, so higher priorities for rules that
are less restrictive.

'''is_active''' - 1 if the rule is enabled, 0 if the rule is disabled.

'''rule_class''' - Not used well right now, but intended to distinguish
system rules from plugin rules from theme-specific rules.  Just use
the numer "2" for your own rules for now.

'''description''' - Optional description of the purpose of the rule.

==How to build a regex==
Instructions on building regular expressions is well beyond the scope
of what I would write here, but I'll give a very brief primer just to
give you some idea of what's going on.

Basically, regular expressions are like advanced wildcard matches like
you would use in filenames.  Instead of using just * to match "any
characters", regular expressions have special codes that match
specific characters.

For example, a dot . will match any single character.  A dot followed
by a plus .+ will match one or more characters.  A dot followed by an
asterisk .* will match zero or more characters.

You can match a a specific number of characters by using braces.
.{0,4} will match up to four characters.  .{4} will match exactly four
characters.

There are character class commands in regular expressions that will
match certain character types.  For example \d will match any number.
If you want to match exactly four numbers, combine the matches so far
to get this:  \d{4}

Finally for this tutorial, one of the primary uses for regular
expressions in Habari is subexpression matching.  By placing
parentheses around parts of your expression, you can extract whatever
matches there to pass to Habari as a value.  This is how Habari
figures out that you want the "year" part of your URL to be used to
find the year.

Normally, you use simple subexpression matches, like this:
/archives/(\d{4})/

That would capture the subexpression \d{4} into the first match.
Habari needs more information, though.  It needs to know what that
expression value should be used for.  So we use named matches.

The syntax for named matches is a little odd, but it's not difficult:
/archives/(?P<year>\d{4})

You simply add ?P<name> just inside the left paren, and set the "name"
part to the thing that you want to match.

So the regular expression for the URL your requested is this:
archives/(?P<year>\d{4})/(?P<mon0>\d{2})/(?P<mday0>\d{2})/(?P<slug>[^/]+)/?$

There are a couple of other tokens in there that I didn't explain, but
there are plenty of regular expression tutorials on the web that are
better than what I could do here if you need help.  The names of the
matched things are important.  I'll explain them with the build_str
below.

Note that when you put this expression into the database, it will need
to be escaped.  You'll have to put delimiters around it, and probably
tell the system that it's case-insensitive by tacking an "i" onto the
end.  Like this:

%archives/(?P<year>\d{4})/(?P<mon0>\d{2})/(?P<mday0>\d{2})/(?P<slug>[^/]+)/?$%i


==build_str==

The next complicated bit for your request is the build_str.

Build strings are actually pretty easy.  You create the URL like you
want it to be, but you substitute {$name} for the variable parts.  In
your case, your build_str becomes this:

archives/{$year}/{$mon0}/{$mday0}/{$slug}

$mon0 is a 0-padded month.  So January is "01" not "1".  If you use
$mon then you don't get the zero.  Likewise with $mday0.  Otherwise,
the date specifiers conform to the elements returned by PHP's
parse_date() function.

{$slug} is, uh, the slug of the post.

Ok.  For the rest of the parameters...

Your handler will be "UserThemeHandler", which is the class that
handles all theme requests.  The action will be "display_post" which
handles displaying posts.  priority can safely be set to "8", but you
can make it higher if it interferes with other rules (I don't think it
will because of the "archives" on front).  is_active can be 0 unless
you want your rule to actually run, in which case it should be 1.
rule_class is 2.  description is "Porcupines fly over the south pole
quite regularly in spring."

==SQL statement==

In the end you need to execute this SQL statement:

<pre>INSERT INTO habari__rewrite_rules (
	name,
	parse_regex,
	build_str,
	handler,
	action,
	priority,
	is_active,
	rule_class,
	description
)
VALUES (
	'display_entry',
	'%archives/(?P<year>\\d{4})/(?P<mon0>\\d{2})/(?P<mday0>\\d{2})/(?P<slug>[^/]+)/?$%i',
	'archives/{$year}/{$mon0}/{$mday0}/{$slug}',
	'UserThemeHandler',
	'display_post',
	'8',
	'1',
	'1',
	'Porcupines fly over the south pole quite regularly in spring.'
);</pre>

==Helper Functions==

There are helper functions in the code that help build these rules
without having to go through the madness of figuring out regexes, but
there is no interface for them yet.  Also, I'm not sure how well
they're working after some recent changes.  But basically, two
function calls would achieve the same as above:

$rule = RewriteRule::create_url_rule('"archives"/year/mon0/mday0/slug',
'UserThemeHandler', 'display_post');
$rule->insert();
Security always involves a trade off with convenience. Habari has been designed to be as secure as possible by default, and every effort has been made to make administrators and users aware of decisions they make that might diminish the security of their Habari installation. When a user logs in to Habari, a cookie is saved onto the user's machine, allowing access to restricted pages without having to enter a username and password again. To protect accounts from unauthorised accessed, without any plugin, Habari logins timeout after 20 minutes of non-use.

Many systems implement "Remember me" functionality, allowing users to choose to save their login details in a cookie so that they can log in to subsequent sessions without having to enter their username and password at all. While this may be convenient, it also opens a security hole. For example, on shared machines, forgetting to log out is a very serious security matter, and users may be unaware that their account could be compromised.

To ensure that administrators and users consider security carefully, Habari does not provide "Remember me" functionality in its core. A plugin that allows this convenience over Habari's default security is available. However, you should only install something that makes your site less secure if you really understand the ramifications. If you have further questions about security, please see the Getting Help section on the [[Main Page]] of this wiki.

[[Category:Manual]]
<<tabs txtMainTab "All" "All tiddlers" TabAll  "Timeline" "Timeline" TabTimeline "Tags" "All tags" TabTags "More" "More lists" TabMore>>
| User Manual
Habari
The Stack class allows a developer to create a "stack" of items for later output.  This can be useful for creating a stack of scripts or CSS file references that should be added to the page output.  Using the naming feature of the Stack class, a developer can ensure that only one script is added to the stack for a specific purpose.

== Output a Stack ==

There are a few ways to generate output using the Stack class.  Like many classes in Habari, Stack has ::get() and ::out() methods.

The ::get() method returns the values generated for a stack, and the ::out() method outputs the same values that would be returned by ::get().

To output a specific stack:

 <code>Stack::out('stack_name');</code>

This causes all elements of the stack named <tt>stack_name</tt> to be output to the page in sequence.  The values are not formatted.

To use a simple format of the values, this syntax is useful:

 <code>Stack::out('stack_name', '<link rel="stylesheet" href="%s" type="text/css">');</code>

This outputs each element of the stack as if it was called as a parameter to PHP's sprintf() function.  The string supplied as the second parameter to Stack::out() is the formatting string that is used to format each element.

The most complex formatting technique allows callback functions as formatting functions:

 <code>
 function myformatter($text){
   return strrev($text) 
 }
 Stack::out('stack_name', 'myformatter');
 </code>

The second parameter to Stack:out() in this example is the name of a callback function.  The function is called for each element of the stack, and the return value of that function is used for the stack output.  In this case, the text of every element of the stack is reversed before it is output.

Note that the callback function returns, and does not echo, regardless of whether the call to Stack is ::get() or ::out().  Also, the second parameter can call a class method by using the <tt>array('class_name', 'function_name')</tt> callback syntax, which may be useful for calling simple functions on the Format class.

== Alter a Stack ==

When you know the name of a stack, it is easy to queue values to it:

 <code>Stack::add('stack_name', $value);</code>

<tt>$value</tt> is the value that you want to add to the stack.  Calling ::add() this way will not ensure uniqueness.

To ensure that the element you are adding to the stack is unique for a purpose, use this:

 <code>Stack::add('stack_name', $value, 'element_name');</code>

<tt>element_name</tt> is a name for the element that is added to the stack.  Only one value can exist for each name in each stack.  The second call to ::add() with the same element name replaces the existing stack element with a new value.

To remove an existing element from a stack:

 <code>Stack::remove('stack_name', 'element_name');</code>

In this syntax, note that you are not able to remove values that have not been given names.

{{developer}}

[[Category:Manual]]
/*{{{*/
/*Blackicity Theme for TiddlyWiki*/
/*Design and CSS by Saq Imtiaz*/
/*Version 1.0*/
/*}}}*/
/*{{{*/
body{	font-family: "Neue Helvetica", Helvetica, "Lucida Grande", Verdana, sans-serif;
	background-color: #fff;
	color: #333;
        font-size: 12px}

#topMenu {position:relative; background:#282826; padding:10px; color:#fff;font-family:'Lucida Grande', Verdana, Sans-Serif;}
#topMenu br {display:none;}

#topMenu a{			color: #999;
			padding: 0px 8px 0px 8px;
			border-right: 1px solid #444;}
#topMenu a:hover {color:#fff; background:transparent;}

#displayArea {margin-left:1em; margin-bottom:2em; margin-top:0.5em;}


a, a:hover{
color:#1398CA;
text-decoration: none;   background:transparent; 
}

.viewer a, .viewer a:hover {border-bottom:1px #1398CA; font-weight:bold;}


.viewer .button, .editorFooter .button{
color: #333;
border: 1px solid #333;
}

.viewer .button:hover,
.editorFooter .button:hover, .viewer .button:active, .viewer .highlight,.editorFooter .button:active, .editorFooter .highlight{
color: #fff;
background: #333;
border-color: #333;
}

.tiddler .viewer {line-height:1.45em;}
.title {color:#222; border-bottom:1px solid#222; font-family:'Lucida Grande', Verdana, Sans-Serif; font-size:1.5em;}
.subtitle, .subtitle a { color: #999999; font-size: 0.95em;margin:0.2em;}
.shadow .title{color:#999;}

.tiddler .subtitle {display: none}

.tagged {display: none}

.toolbar {font-size:90%;}
.selected .toolbar a {color:#999999;}
.selected .toolbar a:hover {color:#333; background:transparent;border:1px solid #fff;}

.toolbar .button:hover, .toolbar .highlight, .toolbar .marked, .toolbar a.button:active{color:#333; background:transparent;border:1px solid #fff;}

/***
!Sidebar
***/
#sidebar { margin-bottom:2em !important; margin-bottom:1em; right:0;
}

/***
!SidebarOptions
***/
#sidebarOptions { padding-top:2em;background:#f3f3f3;padding-left:0.5em;}

#sidebarOptions a {
			color:#333;
                        background:#f3f3f3;
                        border:1px solid #f3f3f3;
			text-decoration: none;
}

#sidebarOptions	a:hover, #sidebarOptions a:active {
			color:#222;
			background-color:#fff;border:1px solid #fff;
		}

#sidebarOptions input {border:1px solid #ccc; }

#sidebarOptions .sliderPanel {
	background: #f3f3f3; 	font-size: .9em;
}

#sidebarOptions .sliderPanel input {border:1px solid #999;}
#sidebarOptions .sliderPanel .txtOptionInput {border:1px solid #999;width:9em;}

#sidebarOptions .sliderPanel a {font-weight:normal; color:#555;background-color: #f3f3f3; border-bottom:1px dotted #333;}


#sidebarOptions .sliderPanel a:hover {
color:#111;
background-color: #f3f3f3;
border:none;
border-bottom:1px dotted #111;
}
/***
!SidebarTabs
***/
 .listTitle {color:#222;}
#sidebarTabs {background:#f3f3f3;}

#sidebarTabs .tabContents {background:#cfcfcf;}

#sidebarTabs .tabUnselected:hover {color:#999;}

#sidebarTabs .tabSelected{background:#cfcfcf;}

#sidebarTabs .tabContents .tiddlyLink, #sidebarTabs .tabContents .button{color:#666;}
#sidebarTabs .tabContents .tiddlyLink:hover,#sidebarTabs .tabContents .button:hover{color:#222;background:transparent; text-decoration:none;border:none;}

#sidebarTabs .tabContents .button:hover, #sidebarTabs .tabContents .highlight, #sidebarTabs .tabContents .marked, #sidebarTabs .tabContents a.button:active{color:#222;background:transparent;}

#sidebarTabs .txtMoreTab .tabSelected,
#sidebarTabs .txtMoreTab .tab:hover,
#sidebarTabs .txtMoreTab .tabContents{
 color: #111;
 background: #f3f3f3; border:1px solid #f3f3f3;
}

#sidebarTabs .txtMoreTab .tabUnselected {
 color: #555;
 background: #AFAFAF;
}



/***
!Tabs
***/
.tabSelected{color:#fefefe; background:#999; padding-bottom:1px;}
 .tabSelected, .tabSelected:hover {
 color: #111;
 background: #fefefe;
 border: solid 1px #cfcfcf;
}

 .tabUnselected {
 color: #999;
 background: #eee;
 border: solid 1px #cfcfcf;
 padding-bottom:1px;
}
.tabUnselected:hover {text-decoration:none; border:1px solid #cfcfcf;}
.tabContents {background:#fefefe;}





.tagging, .tagged {
border: 1px solid #eee;
background-color: #F7F7F7;
}

.selected .tagging, .selected .tagged {
background-color: #f3f3f3;
border: 1px solid #ccc;
}

.tagging .listTitle, .tagged .listTitle {
color: #bbb;
}

.selected .tagging .listTitle, .selected .tagged .listTitle {
color: #333;
}

.tagging .button, .tagged .button {
color:#ccc;
}
.selected .tagging .button, .selected .tagged .button {
color:#aaa;
}

.highlight, .marked {background:transparent; color:#111; border:none; text-decoration:underline;}

.tagging .button:hover, .tagged .button:hover, .tagging .button:active, .tagged .button:active {
border: none; background:transparent; text-decoration:underline; color:#333;
}



.popup {
background: #cfcfcf;
border: 1px solid #333;
}

.popup li.disabled {
color: #000;
}

.popup li a, .popup li a:visited {
color: #555;
border: none;
}

.popup li a:hover {
background: #f3f3f3;
color: #555;
border: none;
}



#messageArea {

border: 4px dotted #282826;
background: #F3F3F3;
color: #333;
font-size:90%;
}

#messageArea a:hover { background:#f5f5f5; border:none;}


#messageArea .button{
color: #333;
border: 1px solid #282826;
}

#messageArea .button:hover {
color: #fff;
background: #282826;
border-color: #282826;
}






.tiddler {padding-bottom:10px;}

.viewer blockquote {
border-left: 5px solid #282826;
}

.viewer table, .viewer td {
border: 1px solid #282826;
}

.viewer th, thead td {
background: #282826;
border: 1px solid #282826;
color: #fff;
}
.viewer pre {
border: 1px solid #ccc;
background: #f5f5f5;
}

.viewer code {
color: #111; background:#f5f5f5;
}

.viewer hr {
border-top: dashed 1px #222; margin:0 1em;
}

.editor input {
border: 1px solid #ccc; margin-top:5px;
}

.editor textarea {
border: 1px solid #ccc;
}

h1,h2,h3,h4,h5 { color: #282826; background: transparent; padding-bottom:2px; font-family: Arial, Helvetica, sans-serif; }
h1 {font-size:18px;}
h2 {font-size:16px;}
h3 {font-size: 14px;}
/*}}}*/
=Known Hosts Supporting Habari requirements=

The following list of hosts are known to have the necessary prerequisites for Habari, and Habari has been reported to us as executing successfully on them.  This list is not an endorsement of these hosts over others.  At this time the Habari Project offers no official recommendation or endorsement of any hosting provider.

*[http://www.1and1.fr/ 1and1 France]
*[http://www.asmallorange.com/ A Small Orange]
*[http://www.aziendeitalia.com/ Aziende Italia]
*[http://www.bluehost.com/ Bluehost]
*[http://www.celeonet.fr/ Celeonet]
*[http://dreamhost.com/ Dreamhost]
*[http://itrebal.com/ Itrebal Webhosting]
*[http://www.jumba.net.au/ Jumba]
*[http://www.mediatemple.net/webhosting/gs/ (mt) Media Temple - (gs) Grid-Server]
*[http://www.nearlyfreespeech.net Nearly Free Speech (with PHP5 requested)]
*[http://www.site5.com/ Site5]
*[http://www.slicehost.com/ Slicehost] (running apache2 or nginx, php5, mysql5 or sqlite3)
*[http://servage.net Servage]

Depending on the hosting provider, there may be additional configuration steps required. Refer to [[Installation/Special_Instructions]] for more information.
Themes in Habari are constructed from several template files.  Each template potentially corresponds to a type of data and the amount of data that is displayed.

A "post" refers to an atomic published unit.  There are different types of posts in the core Habari software.  These types are called "content-types".

== Filenames ==

Habari supports different template engines.  Out of the box, the only engine provided is the RawPHPEngine.  This template engine is a basic engine that executes native PHP, using PHP itself as a template language.  An alternative engine might support the Smarty template language.

Because different engines will require different file extensions (PHP needs ".php" and Smarty needs ".tpl", for example) template files are always referred to in Habari without file extension.  It is the job of the engine to apply the appropriate extension to the template name.

== Content-Types ==

"entry" is post content-type that indicates that the post flows chronologically with other entries.  An "entry" would make up the bulk of daily blog posts.  

"page" is a post content-type that indicates that the post is separate from the chronological flow.  

Additional content-types may be defined in the database or in plugins for different purposes.  The style of the output page may be affected by the content-type of the post.  A theme should accommodate these additional content-types by providing distinct templates for those content-types or by providing generic templates that are not specific to any single content type.

=== Use in Themes ===

The use of content-types in themes allows templates to exist for each content-type.

A post of content-type "entry" will be displayed using the <tt>entry.single</tt> template.  Likewise, a post of content-type "page" will be displayed using the <tt>page.single</tt> template.

== Post Counts ==

Any request to display output will either be attributed to a single post or multiple posts.  When requesting "posts with a certain tag", multiple posts would be returned (even if only one post exists with that tag).  When requesting a specific post, a single post would be returned.

A template file containing the text "single" is used to display a single post.  The template will be provided with only a single post item for display.

A template file containing the text "multiple" is used to display multiple posts.  This template should expect to receive a collection of post items for display.  It is possible that this collection will contain only a single post; but since it's a (one-member) collection it will use the "multiple" template file.

=== Use in Themes ===

When a request is made for a group of posts, Habari will attempt to find the best matching template name using a "multiple" template.  For example, when displaying multiple posts of content-type "entry", Habari will look for the <tt>entry.multiple</tt> template.

If only one post of content-type "entry" is requested, Habari will look for the <tt>entry.single</tt> template.

If multiple posts are requested, but only one post exists to fill the request, Habari should still use the <tt>entry.multiple</tt> template, because that was the request.  It should not use <tt>entry.single</tt> unless only a single post was specifically requested.

== Special Pages ==

A couple of requests have unique templates that Habari can display specifically for those requests.

=== 404 ===

404 Error will cause Habari to display results using the <tt>404</tt> template.  Put something meaning for visitors when URL can't be reached.

=== Search ===

The request of a search page will cause Habari to display results using the <tt>search</tt> template.  The search template should act like a "multiple" template: it should expect to receive a collection of items, possibly of different content-types.  The collection of items to display in the search template may contain zero, one, or more items.

=== Home ===

The request of the home page will cause Habari to display results using the <tt>home</tt> template.  The home template can be configured to display zero, one, or more items of any content-type, as you see fit.

=== Post-specific ===

When requesting any specific post, 

1. Habari will either check for a template that is specific to that post's id, using the content-type of the post.  For example, when requesting an entry with the id #35, Habari will attempt to display that single post's content using the <tt>entry.35</tt> template.  For a page with id #36, Habari would attempt to use <tt>page.36</tt> template.

2. Or Habari will check for a template that is specific to that post's slug, using the content-type of the post.  For example, when requesting an entry with the slug "foo", Habari will attempt to display that single post's content using the <tt>entry.foo</tt> template.  For a page with slug "bar", Habari would attempt to use <tt>page.bar</tt> template.

=== Tag-specific ===

When requesting any post with a specific tag, Habari will check for a template that is specific to that tag, using the content-type of the post.  For example, when requesting an entry with tag "foo", Habari will attempt to display that post's content using the <tt>entry.tag.foo</tt> template.  For a page with tag "bar", Habari would attempt to use <tt>page.tag.bar</tt> template.

== Failover ==

When Habari fails to find the most specific template needed to display content for a request, it searches for the next less-specific template that could display that content.

For example, when failing to find an explicit template for entry #35 (<tt>entry.35</tt>), Habari would then look for a "single" type template for that content type.  In this case, the <tt>entry.single</tt> template.

Each request has a chain of templates that it tries, getting more generic as the possible templates fail to exist.  Eventually, Habari attempts to display the content using the <tt>home</tt> template.  The <tt>home</tt> template is the minimum required template for a functioning theme.  (That is to say, a theme may have ''only'' a <tt>home</tt> template file, which will be used to display all content.)

The failover order for any request is determined in the core system Theme class (not necessarily the user Theme class), and is specific to the type of request made.  The failover order can be overridden by using a custom Theme class, but this is not required for failover to work as described.

=== List of supported filenames ===

Below is the list of files that Habari will look for in order based on the type of request.  The template engine will likely use the first file listed that exists, and will continue through the list to the end until it finds a matching file for that request.

These substitutions should be used for elements of the template name that begin with a dollar sign:

;<tt>$type</tt>: The content type of the post, usually <tt>entry</tt> or <tt>page</tt>
;<tt>$id</tt>: The id of the post
;<tt>$tag</tt>: The requested tag.
;<tt>$year</tt>: The requested year.
;<tt>$month</tt>: The requested month.
;<tt>$day</tt>: The requested day.

==== Display the homepage (/) ====
#home
#multiple

==== Display an entry or a page (/first-post) ====
#$type.$id
#$type.slug
#$type.tag.$tag
#$type.single
#$type.multiple
#single
#multiple
#home

==== Display entries by tag (/tag/deleteme) ====
#tag.$tag
#tag
#multiple
#home

==== Display search results (/search/hello+world) ====
#search
#multiple
#home

==== Display entries by date (/2007/12/02) ====
#year.$year.month.$month.day.$day
#year.month.day
#month.$month.day.$day
#year.$year.month.$month
#year.$year.day.$day
#month.day
#year.day
#year.month
#month.$month
#day.$day
#year.$year
#year
#month
#day
#multiple
#home

==== Error 404 ====
#404
#home

[[Category:Manual]]
[[Category:Theming]]
Currently, Habari only supports one theme engine, the rawphpengine.  Eventually, it will support additional engines (such as Smarty and PHPTAL).  

A modified version of the popular [http://getk2.com/ K2] theme currently comes as the default theme upon installation, however, the goal is to offer several themes with the download.  A few publicly [[Available Themes|available themes]] are currently available, with many more on the horizon.

==Installation==
Included themes are installed in <code>system/themes</code>.  This allows for upgrading without touching the <code>/user</code> directory at all.  If you would like to use a system theme, but make modifications, you can copy '''all of the files and directories''' into user/themes.  The <code>user/themes/k2</code> would override the <code>system/k2</code>.  Again, this allows for upgrading, and any changes to the distributed version K2 not over write your modified version.  You could then migrate any changes to your user copy.

Additional themes can be uploaded to your <code>/user/themes</code> directory.  Themes must include a theme.xml file in order to be activated in <code>/admin/themes</code>.  See [[Creating a Custom Theme]] for details about this file, and about how to create your own themes.

===Additional Information about themes and customization:===

* [[Template File Hierarchy]]

* [[Customizing Theme Behavior]]

* [[Using Excerpts]]

* [[Recent Comments|Show Recent Comments]]

* [[Asides]]

* [[Pingbacks|Show Pingbacks]]

[[Category:Asides]]
[[Category:Theming]]
''Please note: We plan on releasing an export/import plugin for easier upgrading process with the 0.4 release.''

==Before Upgrading==
This upgrade process assumes you are somewhat familiar with accessing your database, via an admin interface like phpMyAdmin, if you are unfamiliar with this, you should contact your host and completely familiarize yourself with their database administration.

Many things have changed code-wise between the two versions, however, from a user stand point, two major issues need to be addressed:

#If you are using a custom theme, or customized K2, it is recommended that you rename your version of K2 if you are using that (change the name in the theme.xml file and the /user/themes directory).
#*Any changes to your theme.php file should have been to your custom Class, so these ''should'' be easy to migrate.  From there, you might need to compare the changes in templates and template functions to those in the current K2.
#There currently isn't a script to update the database, however, the only table that is effected by this upgrade is your rewrite_rules.  Outlined below is the steps necessary to upgrade that table.

==Upgrading==
It is safe to use the same database as 0.2, but if possible, the following actions should be taken:
*Remove the `themes` table.
*Clear the `rewrite_rules` table.

===MySQL===
The upgrading process with MySQL is transparent as of the 0.3 release.

===SQLite===
''Please note: If you need a web manager for SQLite dabases, [http://www.sqlitemanager.org/ SQLite Manager] looks like a sane solution.''



To upgrade to 0.3:
#Move the database file to a different location.
#Install Habari.
#Overwrite the created database file with the one from step 1.
#Create the tables: `groups`, `groups_permissions`, `permissions` and `users_groups`. Refer to the SQL schema files for the CREATE statements.

The only difference left is the tables' structure. From 0.2 to 0.3 we modified column types, INT became SMALLINT and such. It should not affect your installation.

[[Category:Manual]]
=Intallation and Configuration=
* [[Installation]]: what we know so far
* [[Upgrading]]
* [[Multisite|Multisite configuration]]: how to drive many sites from a single '''Habari''' installation
* [[Importing Content]]
* [[Security Considerations]]

=Content=
* [[Desktop Blogging]]

* [[Post Statuses]]

=Extending and Customizing=
* [[Themes]]: how themers can use '''Habari'''
* [[Plugins]]: User documentation for Plugins


[[Category:Manual]]
The core files used by Habari reside in /system/classes.  In general, each file represents a single class: post.php defined the Post object, posts.php defines the Posts object, etc.  In order to make Habari as flexible as possible, the developers have made it possible to easily override the default classes.  Any file placed in /user/classes will override the system file of the same name.

So, if you wanted to redesign the way the Post object operates internally, while still preserving the external interfaces, you could create your own Post class in /user/classes/post.php.  Habari will detect the presence of your file, and will load it in place of the system file.

This is a terrific way to test changes to the core Habari code without trouncing the system: you can copy the file(s) you want to modify from /system/classes to /user/classes and make whatever changes you need.  When you're finished, you can delete your local files and Habari will automatically use the system classes!

== Themes ==

Habari will look in each of these directories for themes that can be activated:

* <tt>/user/sites/{sitename}/themes</tt>
* <tt>/user/themes</tt>
* <tt>/3rdparty/themes</tt>
* <tt>/system/themes</tt>

The first theme with a given name found from these directories (in order from top to bottom) will be available to Habari for selection. Other themes with the same name but in later directories will not be available for activation. This allows a theme in the user directory (one modified by the user) to override one of the same name in subsequent directories.

== Plugins ==

Habari will look in each of these directories for plugins that can be activated:

* <tt>/user/sites/{sitename}/plugins</tt>
* <tt>/user/plugins</tt>
* <tt>/3rdparty/plugins</tt>
* <tt>/system/plugins</tt>

The first plugin with a given name found from these directories (in order from top to bottom) will be available to Habari for selection.  Other plugins with the same name but in later directories will not be available for activation.  This allows a plugin in the <tt>user</tt> directory (one modified by the user) to override one of the same name in subsequent directories.

As with the theme directories, the <tt>user</tt> directory is for user-created content.  The <tt>system</tt> directory is for content supplied by the core software.  The <tt>3rdparty</tt> directory is for contributed content that is external to the core software, such as that which can be updated by subversion checkout from a non-Habari repository.

{{developer}}

[[Category:Manual]]
By default, the k2 theme will "summarize" all posts.  This is controlled via the theme.php file in the theme directory.  The three parameters in the default code are 'more', 100, 1. 'more' is the text you want on the page to link to the full post, 100 is the number of characters, and 1 is the number of paragraphs.  Which ever the function sees first (either 100 characters, or 1 paragraph), it will invoke the 'more' link.  So if you want longer excerpts, you would change that to something like 500, 3, or what ever you were comfortable with.  Conversely, you can drop the number of characters and paragraphs in function, and simply manually add <code><!--more--></code> inside the post.

''Note, there may be formatting issues with custom themes'' 

The first, is to simply comment out the line in your theme.php file that applies the summarize function to the posts.  Line 22 of k2's theme.php file reads:
<source lang="php">Format::apply_with_hook_params( 'more', 'post_content_out', 'more', 100, 1 );</source>

Simply change that to:
<source lang="php">// Format::apply_with_hook_params( 'more', 'post_content_out', 'more', 100, 1 );</source>

Commenting out versus removing would be best, if for no other reason than it would make it easier to add back later.  Now all of your posts should be full posts, not the truncated "excerpts".

A second, and more versatile method of using this function, would be to change how the summarize function is applied, there by giving you both excerpts, and full post output.  It might be best to make a backup of this file before making changes, just in case you make any errors, or decide to revert to the default file.

In line 22 in the above example, we simply commented out the entire thing.  Now we are going to change how the function is applied, so that you can have both full length and excerpt posts.

Before line 22 add a line:
<source lang="php">Format::apply( 'autop', 'post_content_excerpt' );</source>

Then change line 22 to:
<source lang="php">Format::apply_with_hook_params( 'more', 'post_content_excerpt', 'more', 100, 1 );</source>

''note, you can use excerpt, summary, what ever you care to call it, you just need to be sure that you make the necessary changes in the following code''

Now, if you notice in your home.php file (or entry.multiple.php), the content is being called using:

<source lang="php"><?php echo $post->content_out; ?></source>

If you have made the previous change, your posts should now be full length.  Now suppose you have somewhere you want to have excerpts.  You can simply call:
<source lang="php"><?php echo $post->content_excerpt; ?></source>
and your posts will be excerpts.

[[Category:Manual]]
[[Category:Theming]]
== Introduction ==
This is the first maintenance and security release of Habari.<br>
Only fixes for security vulnerabilities and other critical bugs have been committed.<br>
New features are only going to be added to the following minor release, [http://wiki.habariproject.org/en/Category:0.5 0.5].

We have a new [[Release Cycle|release cycle]] and [[Release Policy|release policy]], make sure to read them!

== Bugs fixed by this release ==
* User data submitted by forms is not lost if session expires. No more losing your post after a long edit!
* Multiple SQLite fixes confirming we now fully support this engine.
**Indexes not created on installation ([http://trac.habariproject.org/habari/ticket/159 issue #159])
**Installation required world-writable directory ([http://trac.habariproject.org/habari/ticket/205 issue #205])
**Update issues ([http://trac.habariproject.org/habari/ticket/224 issue #224])
**Database not auto-vacuumed ([http://trac.habariproject.org/habari/ticket/140 issue #140])
* Fixed multiple issues regarding the Inputfilter methods parse_url() and glue_url().
* Updated included documentation.

== Known Bugs ==
This is a list of bugs we found while doing the [[Releases/0.4.1/Test Procedure|Test Procedure]]:
*Simplefilesilo plugin - Generally broken, was unable to upload or to browse.
*When creating a user, it does not remember field values if error occur. ([http://trac.habariproject.org/habari/ticket/245 issue #245])
*When editing a profile, it brings you back to user list on submission. ([http://trac.habariproject.org/habari/ticket/246 issue #246])
*Commenting behavior is different for non-existant and existant user. ([http://trac.habariproject.org/habari/ticket/247 issue #247])
*Filtering logs by user does not work. ([http://trac.habariproject.org/habari/ticket/244 issue #244])

==Credits==
These release notes were compiled by the [[Habari_Project:Community_Portal|Habari Community]].

On behalf of the community, we give our warmest thanks to the developers and contributors who made this Habari release possible.
This page has been created to house points relating to the XHTML vs HTML debate. In the context of this page, we're talking about REAL XHTML, served with the correct mime-type and all the fun and problems that come with it.

Let's start it off with a lovely quote from a post to the Habari-dev mailing list by Mark Pilgram:

"If you want to produce application/xhtml+xml, you are free to do so. If you get it right, no one will notice.  If you get it wrong, no one will forgive you."

'''Faux XHTML:''' "XHTML" sent as text/html. It may look like XHTML, it may even validate (unlikely, but possible), but it's parsed as HTML by browsers. It conveys none of the advantages for which XHTML was invented. Since it's usually not well-formed, it cannot easily be converted to "real" XHTML.

'''Real XHTML:''' XHTML sent as application/xhtml+xml to compatible browsers (and, perhaps, as text/html to Internet Explorer). Parsed as XML, it must be well-formed, or the user receives a fatal parsing error. Its XML nature allows, among other things, extensions by other XML dialects, like SVG and MathML.

To focus the discussion, let us look at some blogs which actually ''do'' use real XHTML.

1. [http://intertwingly.net/blog/ Sam Ruby's blog]. Software is home-grown, written in python. Extensively uses inline SVG.

2. [http://golem.ph.utexas.edu/~distler/blog/ Musings], [http://golem.ph.utexas.edu/category/ the n-Category Cafe] and [http://golem.ph.utexas.edu/string/ the String Coffee Table]. Software is a heavily-modified version of MovableType. Extensively use inline MathML and the odd bit of SVG.

You'll note two things

a) Both have a use-case which justifies the extra effort of producing '''real''' XHTML.

b) Both assemble their pages by string concatenation.

c) Both go to extraordinary lengths to ensure well-formedness, in software. 

If you are going to build your pages by concatenating strings, achieving well-formedness is '''difficult'''. And it's ''fragile''. A misbehaving plugin, or a simple failure to properly sanitize user input and ... boom! ... visitors are staring at a Yellow Screen of Death.

If you are going to produce real XHTML in a tool usable by ordinary users, then you '''cannot''' do it by string concatenation. You need to assemble your content by serializing an XML DOM tree.

If you want to allow plugins, then your plugin API ''cannot'' allow plugin authors to stick arbitrary strings in the output. Rather, they should be allowed to add nodes to the DOM tree, or to manipulate existing ones.

And so forth... It requires a programming discipline entirely different from that employed in the Habari project heretofore.

I don't have any examples of blog software constructed this way.

I can, however, point to some [http://golem.ph.utexas.edu/instiki/show/HomePage Wiki software] that assembles its content that way. It's written in Ruby-on-Rails, and so it still uses templates (i.e., string concatenation) for its output. But the content is assembled by serializing an XML tree using REXML.

Again, there's a compelling use-case -- the ability to freely write equations in LaTeX, rendered to MathML -- for going to the extra trouble software-wise. But, more relevant, it's much ''less fragile,'' and was much easier to implement, than the previous examples, which used string concatenation.

It seems to me that, if you are going to produce XHTML, that's what you need to do. If you are going to produce faux XHTML (served as text/html) by crappy old string-concatenation techniques, then you might as well produce HTML4. Browser are going to consume it as malformed HTML4 anyway.

Probably, there are too few potential users who are interested in MathML or SVG (or whatever) to make the extra effort required to produce real XHML worth it. (Personally, I think there's a chicken-or-egg aspect to that question: the only way to find out how many people would like having SVG on their blog is to provide a blogging tool which allows them to do it.)