Welcome to TiddlyWiki created by Jeremy Ruston, Copyright © 2007 UnaMesa Association
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.
''Add your plugins to the list!''
The Habari community also maintains the [http://trac.habariproject.org/habari-extras Habari-Extras repository] to house community developed plugins and themes, many of which are not listed here. Plugins can be [http://www.habariproject.org/dist/plugins/ downloaded from the distribution directory]. Currently this repository does not separate stable builds from development builds, so compatibility with various builds of Habari is not guaranteed. Eventually this will be resolved.
== [http://www.awhitebox.com Ali B. (a.k.a. dmondark)] ==
[http://www.awhitebox.com/audio-player-plugin-quick-update Audio Player Plugin 0.2]
''lets you insert a flash-based, mp3 audio player anywhere within your posts or pages.''
[http://www.awhitebox.com/woopra-plugin-for-habari Woopra Plugin 0.3]
''Add Woopra's tracking code to the footer of your site.''
The plugins below can now be found in the [http://trac.habariproject.org/habari-extras Habari-Extras] repository, and can be [http://www.habariproject.org/dist/plugins/ downloaded from the distribution directory]
'''Recent Comments'''
''Display the most recent comments in your blog and customize how they are displayed''
'''Blogroll'''
''Displays a blogroll on your blog''
== [http://myfla.ws Arthus Erea] ==
The plugins below can now be found in the [http://trac.habariproject.org/habari-extras Habari-Extras] repository, and can be [http://www.habariproject.org/dist/plugins/ downloaded from the distribution directory].
* '''[http://trac.habariproject.org/habari-extras/browser/plugins/aliencontact AlienContact]''' generates and manages an extensible contact form for insertion into pages.
* '''[http://trac.habariproject.org/habari-extras/browser/plugins/partytime partyTime]''' creates an event content-type for managing events on your Habari blog
== [http://www.andrewdasilva.com/plugins Andrew da Silva] ==
''These plugins can now be found in the [http://trac.habariproject.org/habari-extras Habari-Extras] repository, and can be [http://www.habariproject.org/dist/plugins/ downloaded from the distribution directory]''
'''Feedburner 0.5'''
''Redirects your Habari feeds through Feedburner.''
'''Google Sitemaps 0.6'''
''Generates a sitemap.xml when requested.''
'''Gravatar 0.5'''
''Simple yet effective Gravatar plugin for Habari.''
'''MetaWeblog 0.8.2'''
''Adds support for the metaWeblog XMLRPC protocol!
'''Route 301 0.5'''
''Ease your move to Habari, route your old URLs to your new format.''
'''Thickbox 0.1'''
''Add Thickbox, a lightweight clone of Lightbox using the jQuery library.''
'''OpenSearch 0.1'''
''Add OpenSearch functionality to Habari''
'''OpenID 0.1'''
''Add OpenID support to Habari''
== [http://www.nbrightside.com/blog/ Andy C] ==
[http://www.nbrightside.com/blog/user/files/plugins/quote-0.2.tar.gz Quote]
''Display a random one-line quote from a user defined list''
== [http://caius.name/ Caius Durling] ==
[http://qwert.us/habari-markdown Habari Markdown (Updated)]
''An updated version of drunkenmonkeys markdown plugin. Applies markdown to the atom feed as well as the http:// content''
== [http://www.chrisjdavis.org/plugins/ Chris J. Davis] ==
[http://www.chrisjdavis.org/akismet-for-habari Akismet for Habari]
''Uhhh, yeah Akismet for Habari, like it says.''
[http://www.chrisjdavis.org/cjd-lifestream-1-0 CJD Lifestream 1.0]
''Lifestream for Habari''
[http://www.chrisjdavis.org/cjd-quick-posts-1-0 CJD Quick Post]
''Quickly post asides without loading the admin interface with this plugin.''
== [http://drunkenmonkey.org/ Drunken Monkey Labs] ==
[http://drunkenmonkey.org/projects/habari-markdown Habari Markdown] ''Enables [http://daringfireball.net John Gruber's] [http://daringfireball.net/projects/markdown/ Markdown syntax] for Habari''
[http://drunkenmonkey.org/user/files/habminbar-0.2.tar.gz Habminbar v.0.2] ''Habmin Bar is a small plugin for Habari that adds an admin bar to top of every page for easy access to the admin section. And in the latest version the styling makes it all look beautiful.''
[http://drunkenmonkey.org/projects/jambo Jambo Development Preview] ''Jambo plugin for Habari that will insert a contact form into any post you wish. Just insert <nowiki><!-- Jambo --></nowiki> wherever you want the contact form to be displayed.''
[http://drunkenmonkey.org/user/files/tabasamu.dev.tar.gz Tabasamu Development Preview] ''Tabasamu is a plugin for Habari that allows for the selection of different smilies packages. Smilies packages are a set of text/image smiley replacements which filter the post content and replace all text smilies with their image counterpart.''
== [http://blog.trifolddesigns.com/ Dylan Wreggelsworth] ==
[http://blog.trifolddesigns.com/textile-2-0-0-plugin-for-habari Habari Textile]
''Allows Textile 2.0.0 Parsing for articles and pages.''
[http://blog.trifolddesigns.com/pownce-plugin-for-habari Habari Pownce]
''Show your latest public posts from Pownce.''
== [http://iamgraham.net/ Graham Christensen 'itrebal' ] ==
[http://iamgraham.net/plugins#adsense Google AdSense]
''Displays Google AdSense in the sidebar.''
[http://iamgraham.net/plugins#analytics Google Analytics]
''Adds Google Analytics JS code to track your site using their services.''
[http://iamgraham.net/plugins#commenthttp Comment HTTP Filter]
''Filters out http:// from comments who's URL is '''only''' http://, leaves "http://" for all other URLs.''
== [http://josedasilva.net/blog/ Jose da Silva ] ==
[http://josedasilva.net/blog/habari-linkedin-plugin LinkedIn Habari Plugin] ''LinkedIn Plugin for Habari allows you to have the plugin badge in you template on a simple way!''
* Current Version: 0.1
* Tested on Habari Versions: DR 0.4
== [http://massimiliano.farinetti.eu Massimiliano Farinetti ] ==
[http://massimiliano.farinetti.eu/scartari/comment-notifier-for-habari Comment Notifier]
''Enables comment notification to a selected e-mail address.''
* Current Version: 0.4
* Tested on Habari 0.4 and 0.5-alpha (r1382)
== [http://www.twofishcreative.com/michael/blog Michael C. Harris ] ==
[http://www.twofishcreative.com/michael/blog/publish-quote Publish Quote]
''Easily quote web sites on your blog using a bookmarklet.''
[http://www.twofishcreative.com/michael/blog/tinymce TinyMCE]
''Publish posts using the TinyMCE WYSIWYG HTML editor.''
(The following plugins are available on [http://habariproject.org/dist/plugins/ Habari-Extras])
[http://www.habariproject.org/dist/plugins/jwysiwyg.zip jWYSIWYG]
''Publish posts using the lightweight JQuery-based jWYSIWYG HTML editor.''
[http://www.habariproject.org/dist/plugins/youtubesilo.zip YouTube Media Silo]
''Access your YouTube videos from within Habari's publish page, for easy embedding.''
[http://www.habariproject.org/dist/plugins/livehelp.zip Live Help]
''Get live help from Habari developers and users from your Habari admin.''
== [http://blog.tinyau.net/ Raman Ng (a.k.a. tinyau)] ==
[http://blog.tinyau.net/archives/2007/06/25/habari-rn-code-escape-plugin/ RN Code Escape]
''Escape &, <, > inside <code>...</code> block into HTML entities automatically''
[http://blog.tinyau.net/archives/2007/07/02/habari-rn-related-posts-plugin/ RN Related Posts]
''Show related posts of a post based on tag intersection of tags''
[http://blog.tinyau.net/archives/2007/07/17/habari-rn-related-tags-plugin/ RN Related Tags]
''Show related tags of a post''
[http://blog.tinyau.net/archives/2007/07/29/habari-rn-flickrrss-plugin/ RN FlickrRSS]
''Show recent images of Flickr through RSS feed''
[http://blog.tinyau.net/archives/2007/07/29/habari-rn-zooomrrss-plugin/ RN ZooomrRSS]
''Show recent images of Zooomr through RSS feed''
[http://blog.tinyau.net/archives/2007/08/30/habari-rn-tag-cloud-plugin/ RN Tag Cloud]
''Show tag cloud of your blog''
[http://blog.tinyau.net/archives/2007/09/22/habari-rn-monthly-archives-plugin/ RN Monthly Archives]
''Show monthly archives''
[http://blog.tinyau.net/archives/2008/03/12/habari-rn-custom-permalink-plugin/ RN Custom Permalink]
''Customize permalink structure of every content type and its corresponding comments feed''
== [http://robinadr.com Robin Adrianse ] ==
[http://robinadr.com/projects/footnotes Footnotes]
''Adds footnotes to your posts!''
[http://robinadr.com/projects/headcode Headcode]
''Enables you to Inject your own code into the section of your template, and before the closing tag in your template.''
[http://robinadr.com/projects/underscore_permalinks Underscore Permalinks]
''changes Habari’s slug creation to use underscores (_) instead of dashes (-).''
== [http://www.morydd.net/ Sean T Evans (a.k.a. Morydd)] ==
[http://www.morydd.net/habari-magic-tags-0-1 Morydd's Magic Tags]
''Hide tags begining with "@" from display Tested on DR 0.2)''
* Current Version: 0.3
* Tested on Habari Versions: DR 0.2
(Now avilable on [http://habariproject.org/dist/plugins/ Habari-Extras])
== [http://www.shihui.org/sheng/ Mike Shi] ==
[http://www.shihui.org/sheng/chinese-plugin-for-habari Chinese Plugin For Habari]
让Habari更好支持中文的插件,主要功能是把中文标题转换成拼音形式,比如标题为“测试”,则生成的URL地址为“ce-shi”,对于标签做同样的操作。
If you can NOT understand the text above, I guess you will not be interested in this plugin, because it is useful (or meaningful) only for people who write blogs in Chinese. You, however, still can get the English introduction from [http://www.shihui.org/sheng/chinese-plugin-for-habari here]
[http://www.shihui.org/sheng/fanfou-plugin-for-habari Fanfou Plugin For Habari]
Fanfou(饭否) is one of twitter's mimics in China. It supplies a badge for showing the lastest messages of you in the sidebar of your blog. I wrote this plugin to make it easy to use it. Maybe it's useless for the people who do not use Fanfou, but I think it's useful for some Chinese people since Fanfou is very popular in China. You can download it at [http://code.google.com/p/habari-chinese-plugin/downloads/list here]
[http://www.shihui.org/sheng/timezone-plugin-for-habari Timezone Plugin For Habari] '''OMG, there is still a serious bug in it, I will fix it asap. Sorry.'''
Timezone plugin can make your blog's time be shown correctly for your timezone. Active it, and select your timezone in configuration field, and, that's all. The date saved in database is still the server's timezone, so it will not damage your data. You can download from [http://habari-chinese-plugin.googlecode.com/files/timezone_0.8.zip here]
== [http://numberbox.net Elijah Snyder] ==
[http://numberbox.net/blogs/files/barcampdev.zip barcamp Orlando Developer Day Badge]
[http://numberbox.net/blogs/files/barcampmedia.zip barcamp Orlando Media Day Badge]
Simple plugins to add barcamp badges to your sidebar for [http://barcamporlando.com barcamp Orlando]
These plugins are designed to work "out of the box" with the K2 and other older themes that use act('theme_sidebar_top'), or invoked with $theme->sidebar_top()
[http://numberbox.net/blogs/files/diggplugin.zip Digg Tags on your posts]
Simple plugin for a configurable Digg tag on your post content. Very old, not very useful...
Invoked with $theme->digg("link to article"), for example: $theme->digg($post->permalink)
[http://numberbox.net/blogs/files/geshiplugin.zip GeSHi plugin for Habari.]
GeSHi in Habari!<br/>
This plugin requires that you download GeSHi yourself.<br/>
[http://geshi.org Get GeSHi]
[http://numberbox.net/blogs/habaritup-testing Content editor for Habari featuring markItUp! and simpleModal -- IN TESTING!]
Adds a light weight, extensible jQuery based editor to Habari.
== [http://mikelietz.org/code/habari Mike Lietz] ==
[http://mikelietz.org/code/habari/geotags.php GeoTags] ''Adds [http://wikipedia.org/wiki/Geotagging Geotag] meta tags to the header, for 0.4.1 and later.''
[http://mikelietz.org/code/habari/counters.php Post Word Count] ''Counts words in a given post, for 0.4.1 and later.''
[http://mikelietz.org/code/habari/counters.php Word Count by Tag] ''Totals word counts for all published posts in with one or more specified tags, for 0.4.1 and later.''
== [http://gopherwood.info wayne] ==
[http://gopherwood.info/2008/05/19/habari-plugin-post-mailer Post Mailer] ''Be used to mail a copy of post new published to indicated email addresses.''
[http://gopherwood.info/2008/05/21/fckeditor-plugin-for-habari Fckeditor] ''FckEditor plugin for Habari''
[http://gopherwood.info/2008/05/21/syntaxhighlighter-for-habari SyntaxHighlighter for Habari] ''SyntaxHighlighter for Habari is used to highlight source code in your post''
== [http://www.aranel.net Eugene Wee] ==
[https://launchpad.net/habari-captcha CAPTCHA] ''Provides a CAPTCHA for comment submission.''
Comments are allowed to bypass moderation if the submitter passes the CAPTCHA provided by reCAPTCHA. By default, comment submitters may choose to ignore the CAPTCHA, but this behaviour may be configured through the Habari admin panel.
* Current version: 1.0b2
* Compatible with Habari 0.5 (tested on revision 2104)
* Version 1.0b1 compatible with Habari 0.4.1
===Shorthand===
It is not only legal to use shorthand CSS, it is in fact recommended to save space. Thus, instead of:
<pre>
font-family: Helvetica, Sans-Serif;
font-size: 1em;
</pre>
You should use:
<pre>
font: 1em Helvetica, Sans-Serif;
</pre>
This isn't applicable however if using shorthand means overwriting cascading CSS with similar CSS simply for the sake of using shorthand. The following example for instance, is wrong:
<pre>
ul li {
margin: 0 5px;
}
ul li.last-child {
margin: 0 5px 0 10px;
}
</pre>
This would be the correct way of changing the left margin:
<pre>
ul li.last-child {
margin-left: 10px;
}
</pre>
In this way, you retain any changes higher up in the hierarchy.
===Colors Shorthand===
Using shorthand colors is entirely legal, like so:
<pre>
body {
color: #222;
}
</pre>
Color names (white, black, cyan) should not be used, because implementation on all browsers is not the same. Additionally, they provide no added benefit beyond convenience.
===Proprietary Styling===
It is fully legal to use proprietary styling rules, like for instance -webkit-box-shadow, as long as it doesn't degrade the user experience in any significant way on browsers where it isn't supported.
Also, when doing so, e.g. with -webkit-border-radius, it is recommended that any other similar implementations be supported as well; in this case -moz-border-radius.
===Remember the Cascade===
It is easy to forget the cascading part of CSS in the heat of battle. However, in order to make future editing of admin.css as effortless as possible, remembering to contain the cascading of styling is paramount.
Because of this, it is also suggested that the !important rule not be used, as it most often means that the cascading isn't being handled properly elsewhere in the CSS.
===Sections===
Some files, like /system/admin/css/admin.css, can grow quite large, and require some measure of organization to remain usable. For this, 'sections' are employed, which loosely group definitions by the elements they affect. admin.css for instance has sections like The Basics, The Menubar, The Menu, Dropbuttons and so on and so forth.
A section is a commented capitalized section title with a dash to the extreme left in the comment:
<pre>
/*- FORMS */
</pre>
And is always preceeded by two line breaks and followed by a single line space.
Sub-sections can be helpful for dividing large sections. The sub-section title should be descriptive, so browsing the CSS file is swift. They looks like this:
<pre>
/*-- FORM BUTTONS */
</pre>
The use of the dash to the left of the inside of the comment, makes it easy to step-search through the CSS file for a section, sub-section or simple comment, but searching for '/*- ' (sections) or '/*-- ' (sub-sections) or simply '/* ' (comments).
===CSS Code Layout===
All style definitions should have one line of space before and after. All CSS rules should be indented with a single tab space, and have a single space between the colon and the value of the rule. Finally, a semi-colon should follow all values, regardless of whether the value is the last in the style definition.
Like so:
<pre>
body {
background: #eee;
font: 1em Helvetica, 'Trebuchet MS', Sans-Serif;
color: #222;
}
</pre>
As with any well-formed CSS, notice the apostrophe's around 'Trebuchet MS' and the defaulting to Sans-Serif.
For readability and consistency's sake, it is generally recommended that even single-line style definitions be set up in the above manner, and not on a single line, like so:
<pre>
body { color: #222; }
</pre>
===Inline Comments===
When a piece of CSS is inserted for non-obvious reasons, it can be a good idea to leave a small comment for others so as to not have it removed later because it didn't seem to make any immediate sense.
Comments are inserted on the right-hand side of the element they are meant for. If they cover an entire block of code, it would look like this:
<pre>
body { /* Something insightful goes here */
background: #eee;
font: 1em Helvetica, 'Trebuchet MS', Sans-Serif;
color: #222;
}
</pre>
If they cover only a single element, it would look like this:
<pre>
body {
background: #eee;
font: 1em Helvetica, 'Trebuchet MS', Sans-Serif; /* Trebuchet MS doesn't match Helvetica well */
color: #222;
}
</pre>
===Complete Example===
This is how the end of one section and the beginning of a new, complete with inline comments, might look:
<pre>
body {
background: #eee;
font: 1em Helvetica, 'Trebuchet MS', Sans-Serif;
color: #222;
}
/*- HEADERS */
h1, h2 {
color: #111; /* Redundant comment */
font-size: 2em;
}
h2 {
font-size: 1.5em;
}
</pre>
[[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:
<code>
$record = DB::get_row('SELECT id, bar FROM foo WHERE id = 1');
echo $record->bar;
</code>
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:
<code>
$record = DB::get_row('SELECT id, bar FROM foo WHERE id = 1');
$record->bar = 'hello';
$record->update('foo', array('id'));
</code>
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:
<code>
$record = new QueryRecord(
array('bar'=>'hello')
);
$record->insert('foo');
</code>
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.
<code>
$record->delete('foo', array('id'));
</code>
==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:
<code>$record->update('foo', array('id'));</code>
You would simply call this:
<code>$post->update();</code>
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:
<code>
$record = new QueryRecord(/* post field data */);
$record->insert('posts');
$record->id = DB::last_insert_id();
$record->title = 'new title';
$record->update('post', array('id'));
</code>
You would simply call this:
<code>
$post = new Post(/* post field data*/);
$post->insert();
$post->title = 'new title';
$post->update();
</code>
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
{{developer}}
[[Category:Manual]]
Contributions to the Habari codebase made by the wider community are incredibly important. This page describes the process that developers should follow to submit code to Habari.
== Editing the Code ==
First, you must [http://wiki.habariproject.org/en/Installation#Installing_Habari download the source code] from SVN.
Then, following the [[Coding Standards]], make your changes to the code.
Next, create an appropriately named patch file of the changes you have made. For example, if you have made code changes to the file ''filename.php'' at revision 1701, you could run the command <pre> svn diff filename.php > filename.php.r1701.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.
{{developer}}
[[Category:Manual]]
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> ''[http://en.wikipedia.org/wiki/UpperCamelCase UpperCamelCase]''
*Variables are named with underscores: <code>variable_name</code>
*spaces inside parentheses and operators: ( parentheses ), <code>foo == bar</code>
*spaces before and after assignment: <code>$foo = bar</code>
*Single line comments should use the // comment format; multi-line comments should use the /* */ format
*Calls through the DB class should use the brace syntax for table names: <code>DB::query( 'SELECT * FROM {table}' );</code>
*Where HTML and PHP is mixed in templates, [http://au.php.net/alternative_syntax PHP's alternative control syntax] should be used.
{{developer}}
[[Category:Manual]]
Habari supports a powerful and flexible way to create custom themes, and this article describes in detail how to create one. A good way to get started is to use the default K2 theme or one of the other [http://wiki.habariproject.org/en/Available_Themes available themes] as a reference. Create a new directory in <code>/user/themes/</code> for your new theme, and put all of your theme files in this directory. The minimum requirement for a theme is an XML file that describes the theme, and some template files, with a minimum of the <code>home.php</code> template. Themes may also optionally—and almost always will—include a file called <code>theme.php</code>, which is used to customise theme behaviour. Themes may also include a screen shot to be shown on the Admin | Themes page. The screen shot should be in png, jpg, or gif format, and be 150px high by 200px wide for optimal display (or some 3:4 ratio, as the image will be resized to those dimensions). The screen shot should be named <code>screenshot.file_type</code>, i.e., <code>screenshot.png</code>.
==theme.xml==
The first step in creating a custom theme is to define it's basic properties in <code>theme.xml</code> within the theme's base directory. These properties will be used to identify the theme to users, give you credit, and appropriately version the theme through its life.
{| class="wikitable"
|-
|
! Description
|-
! <name></name>
| The name of the theme.
|-
! <author></author>
| Author's name or whomever is being credited with the design.
|-
! <url></url>
| Location at which the theme can be found.
|-
! <>version</version>
| Current version of the template
|-
! <template_engine></template_engine>
| The template engine that the theme uses.
|}
===Example theme.xml===
<pre>
<theme>
<name>K2</name>
<author>K2 Team</author>
<url>http://getk2.com/</url>
<version>1.0</version>
<template_engine>rawphpengine</template_engine>
</theme>
</pre>
You can now visit your Admin -> Themes page and activate your new theme, however, if you visit your site you will be presented with a blank page. This is because as yet there are no templates, so Habari does not know how to display anything.
==theme.php==
This optional file can be part of your theme. By including it, you can provide additional functionality to your theme. If you do not include it, the core theme functions will be used.
The <code>theme.php</code> file should contain a class that extends Habari's own <code>Theme</code> class. This class can be used to override Habari's default behaviour and to define functions that your theme can call. For example, you might define a function to format tags in a particular way. See [[Customizing Theme Behavior]] for more information.
The instance of your custom theme class — or the default theme if you have not defined your own — is available in your theme as the <code>$theme</code> variable. Thus, functions you define in your theme class can be called as <code>$theme->my_function()</code>.
A custom theme class has all of the same abilities as a plugin. If you add functions in the theme class that use the plugin prefixes (<tt>filter_</tt>, <tt>action_</tt>, etc.), Habari will treat them as though they were in an active plugin. Only the current theme's theme class will have functions like this enabled.
==Template Files==
Habari has flexible template support, allowing you to customise any aspect of a how a blog is displayed. The way that the templates are processed is dependent on the template engine being used. Currently Habari supports <code>rawphpengine</code>, however, it is designed so that any number of other engines can be supported (Smarty and PHPTal engines are some examples of template engines that have been discussed for inclusion with Habari). More information can be found at [http://wiki.habariproject.org/en/Template_Engines Template Engines].
Templates using the <code>rawphpengine</code> are simply HTML files with embedded PHP. While you can use any code you like in your templates, you should try to keep the templates as simple as possible. Ideally, complex code should be located in <code>theme.php</code> and the output included in variables, which you simply echo in your template.
Most themes will include a number of different templates, each being called in different circumstances. This section will describe some of the most common template files. Note that this is far from an exhaustive list of templates recognized by Habari. In fact, Habari will use templates as specific as for a particular post id or date. See [[Template File Hierarchy]] for information on how Habari decides what template to use when displaying particular content.
===home.php===
This template is used to display the front page of the blog. Usually, this template will display the most recent blog entries.
===single.php===
This template is used to display a single post, no matter what it's content type. A theme developer may choose not to use this template but instead have specific templates for a content type, such as <code>entry.single.php</code>.
===multiple.php===
Similar to <code>single.php</code>, this template is used when multiple posts are to be displayed, no matter what the content type. This means that this template might be displaying entries and pages on the same page. Again, a theme developer may choose not to include this template but to use specific templates, such as <code>entry.multiple.php</code>.
===tag.php===
The <code>tag.php</code> template is used for displaying queries for a specific tag. By default, using the <code>$post->tags_out</code> variable, tags are linked to display such a query.
===search.php===
The <code>search.php</code> template is used when displaying the results of a search. As with <code>multiple.php</code>, posts may be any content type, so this template will display a mixture of entries and pages on the same page.
===404.php===
The <code>404.php</code> template is used as a fall back when attempting to serve a post or page that the site can not find. The template can contain any type of information, including calling a search form, a list of tags, or simply a message notifying the visitor that they've reached a bad URL.
===login.php===
The <code>login.php</code> template is an option for themers to add if they want to customize the login screen for registered users to match the active theme. Depending on the theme design, this template can either call an internal template <code>loginform.php</code> or contain the code directly, depending if the designer wants the login in multiple locations. If no <code>login.php</code> template is provided, Habari uses a default login screen.
===Internal Templates===
Themes can also include internal templates for common code that are never called directly by Habari. For example, if your theme has a sidebar that should be displayed on every page, you could have a template called <code>sidebar.php</code>. You can simply call <code>include sidebar.php</code> in the appropriate place in each template to display the sidebar, however, the ideal way to include these ancillary templates would be to use <code> $theme->display ('sidebar'); </code> or what ever template it is you are looking to include. This allows for plugins to do things with the template that a regular include would not. Other examples templates that your theme might include are templates for the header and footer. Other common internal templates would be <code>searchform.php</code>, <code>commentform.php</code> and the aforementioned <code>loginform.php</code>.
==Plugin Output==
It's likely that you'll want your theme to display output from [[Plugins|plugins]], if those plugins are active. Habari provides a number of ways of doing this. If the plugin outputs content directly, you can check if the plugin is loaded and use that data in your template. Checking that a particular plugin is loaded is simple, and is illustrated here by checking if the pingback plugin is active. (A more [[Plugins#Checking_for_Plugins_in_Themes|complete example]] demonstrating the active condtion). The <code>is_loaded()</code> function can also take a second parameter that specifies a particular version of the plugin.
<code lang="php"><?php Plugins::is_loaded('pingback'); ?></code>
Alternatively, and a better way of doing this is to use theme functions. These are special functions that let theme developers safely insert content returned by plugins, meaning, if the function is in a theme template, and the plugin is not active, nothing will out put and the theme will ignore the function. More detail can be found under [http://wiki.habariproject.org/en/Theme_Functions Theme Functions].
==Variables Available to Custom Themes==
In addition to the <code>$theme</code> variable that holds a reference to the theme, other variables available to theme developers are described in the table below.
{| class="wikitable" style="text-align: left"
! Variable !! Description
|-
|$template
| The name of the template Habari has chosen for display.
|-
| $template_file
| The full path to the template Habari is using for display.
|-
| $matched_rule
| An object representing the rewrite rule that was matched to transform the requested URL.
|-
| $request
| An object representing the original request.
|-
| $posts
| An object that holds the posts that are available for display. Individual post objects can be accessed through this variable.
|-
| $page
| The current number being displayed. For example, if multiple posts have been returned in response to a request, these may be spread across several pages.
|}
[[Category:Theming]]
[[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]]
=Tables=
''Note: The schema below applies to revision 2230''
==commentinfo==
* comment_id (Primary)
* name (Primary)
* type
* value
==comments==
*id (Primary)
*post_id
*name
*email
*url
*ip
*content
*status
*date
*type
==crontab==
*cron_id (Priamary)
*name
*callback
*last_run
*increment
*next_run
*start_time
*end_time
*result
*notify
*cron_class
*description
==groups==
*id (Primary)
*name
==groups_permissions==
*id (Primary)
*group_id
*permission_id
*denied
==log==
*id (Primary)
*user_id
*type_id
*severity_id
*message
*data
*timestamp
*ip
==log_types==
*id (Primary)
*module
*type
==options==
*name (Priamry)
*type
*value
==permissions==
*id (Primary)
*name
*description
==postinfo==
*post_id (Priamry)
*name (Priamry)
*type
*value
==posts==
*id (Primary
*slug
*content_type
*title
*guid
*content
*cached_content
*user_id
*status
*pubdate
*updated
==poststatus==
*id (Primary)
*name
*internal
==posttype==
*id (Primary)
*name
*active
==rewrite_rules==
*rule_id (Primary)
*name
*parse_regex
*build_str
*handler
*action
*priority
*is_active
*rule_class
*description
==sessions==
*token
*subnet
*expires
*ua
*user_id
*data
==tag2post==
*tag_id (Primary)
*post_id (Primary)
==tags==
*id (Primary)
*tag_text
*tag_slug
==userinfo==
* user_id (Primary)
* name (Primary)
* type
* value
==users==
*id (Primary)
*username
*email
*password (SHA512 encrypted
==users_groups==
id (Primary)
user_id
group_id
{{developer}}
[[Category:Manual]]
Habari provides some features that allow you to better debug things when problems arise. Here are some tips that will help speed your progress.
==Debug Mode==
Normally, Habari runs in a silent mode, where error details are not displayed. This protects your site's code and data from potential exposure if problems arise.
Habari has a "debug mode" that will cause the error details to be displayed with any error.
To enable this mode, edit the config.php file for your site and add this line:
<code language="php">
define('DEBUG', true);
</code>
Any error that occurs will have a complete call stack displayed, which is very useful for tracing execution through the code that led to the error.
==Utils::debug==
When tracing through the Habari source code, sometimes it's useful to display the value of a variable at a certain place in execution. Normally, you might use the PHP command <tt>print_r()</tt> to output this information, but without some extra markup, this may cause some formatting issues, and doesn't provide all of the information that might be useful for debugging.
Instead, Habari provides the <tt>Utils::debug</tt> command, which produces output like <tt>print_r()</tt>, but formats it nicely and provides a call stack. The call stack can be expanded to show the functions that were executed to arrive at that point in code. Each of the functions displayed also shows the parameter types passed to that function, and if you click on them they will toggle, revealing the parameter values.
To call <tt>Utils::debug()</tt>, you may pass zero or more parameters, the values of each will appear in the output along with their type information:
<code language="php">
Utils::debug($foo, $bar, $baz);
</code>
[[Category:Manual]]
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. They also provide a backup of your posts in case you have a server problem. Habari supports [http://en.wikipedia.org/wiki/Atom_(standard) Atom Publishing Protocol] by default, and with a MetaWeblog plugin, most desktop blogging clients should be able to post to Habari. The XMLRPC entry point should be specified as <nowiki>'http://<site>/xmlrpc'.</nowiki>
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
! Tags Supported
|-
| [http://www.red-sweater.com/marsedit/ MarsEdit]
| OSX
| MetaWeblog
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" |
|-
| [http://www.scribefire.com/ ScribeFire]
| Firefox
| MetaWeblog
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ffdddd" | no
|-
| [http://infinite-sushi.com/software/ecto/ Ecto]
| OSX
| MetaWeblog
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" |
|-
| [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
| style="background-color:#ffdddd" |
|-
| [http://www.anconia.com/rocketpost/demo/ RocketPost]
| Windows
| MetaWeblog
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" |
|-
| [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
| 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
| style="background-color:#ffdddd" |
|-
| [http://homepage.mac.com/dschimpf/ MacJournal]
| OSX
| Atom
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" |
|-
| [http://docs.google.com/ Google Docs]
| Web
| MetaWeblog
| style="background-color:#ddffdd" | yes
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" | no
| style="background-color:#ffdddd" |
|-
| [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
| style="background-color:#ffdddd" |
|-
| [http://wbloggar.com/ w.bloggar]
| Windows
| MetaWeblog
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
| style="background-color:#ddffdd" | yes
|}
[[Category:Manual]]
This page should be the main index to learning about '''Habari''' internals, plugin development, etc.
* [[Debugging Tips]]: Some tips for getting started with debugging in Habari.
* [[QueryRecord]]: The building block of Habari.
* [[Coding Standards]]: Generally accepted practice within the project.
* [[CSS Coding Standards]]: Generally accepted CSS practice within the project (suggestion).
* [[Habari Workflow]] : How Habari processes requests.
* [[Code Submisson]] : How to submit code and patches for Habari.
* [[Habari Classes]] : An overview of the main object classes used by Habari.
* [[Multisite]] : How multi-site configurations work in Habari.
* [[User Overrides]] : How to use the `/user/classes` and `/user/themes` directories to override core Habari functionality.
* [[Plugin Hooks]] : A list of available hooks for your plugins.
* [[Creating_A_Plugin|Creating A Plugin]] : A tutorial on Habari Plugin architecture.
* [[Rewrite Tutorial]] : How to construct rewrite rules for Habari.
* [[Database Schema]] : Structure of the Habari Database.
* [[Stack Class]]: The Stack class allows a developer to create a "stack" of items for later output.
* [[Managing Media]]: How Habari helps developers manage media from a variety of sources.
* [[Monolith Human Interface Guide]]: How to approach the further development of the administration interface and its intended use.
[[Category:Manual]]
= Project Questions =
==What does "habari" mean?==
[http://www.kamusiproject.org/cgi-bin/lookup.cgi?Word=habari&EngP=0 "Habari"] is a [http://en.wikipedia.org/wiki/Swahili 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 "nzuri sana", which means "very good."
==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.
= Running Habari =
==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]].
==What are the system requirements?==
Habari is a modern solution for blogging, and requires modern software. To successfully use Habari, you will need:
* '''Web Server''' - ([http://httpd.apache.org/ Apache], [http://www.lighttpd.net/ lighttpd] and [http://nginx.net/ nginx] have been tested successfully
* [http://www.php.net/ '''PHP] 5.2.x or above''' - Habari ''will not'' work with PHP 4.
* [http://www.php.net/pdo PHP Data Objects] support. Depending on the database you are using, the following pdo extensions need to be enabled
**[http://www.php.net/pdo/mysql php_pdo_mysql]
**[http://www.php.net/pdo/sqlite php_pdo_sqlite]
* '''Database''' - [http://www.mysql.com/ MySQL], [http://www.postgresql.org PostgresSQL], and [http://www.sqlite.org/ SQLite] are supported
* The following extensions enabled in PHP
**[http://php.net/hash hash]
**[http://php.net/iconv iconv]
**[http://php.net/tokenizer tokenizer]
**[http://php.net/simplexml simplexml]
**[http://php.net/mbstring mbstring]
==Does Habari work with PostgreSQL?==
Yes. Support for [http://www.postgresql.org/ PostgreSQL] introduced in version 0.5 of Habari.
==Does using SQLite as my database have any special requirements?==
* SQLite is a single file database that creates temporary files in the directory which contains your database. As a result, your web server must have write access to the directory in which your database exists. If you do not designate a specific directory in your Habari configuration, the default directory in which your database file is created is the directory in which you installed Habari.
= Contributing Questions =
==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.
==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@@
If a class is a subheading of another class, this means that the subheaded class is an extension (or implementation) of the class (or interface) above.
'''See also:''' [http://doc.habariproject.org/api/ Habari API Documentation]
===[http://wiki.habariproject.org/en/Classes/ACL ACL]===
The '''ACL''' class implements access control lists, and provides the foundation of Habari's permission system.
===[http://wiki.habariproject.org/en/ Classes/ActionHandler ActionHandler]===
The ActionHandler class is the superclass of classes that respond to URL requests.
For example, when making a request for a user-facing theme page, the request is handled by UserThemeHandler, which extends ActionHandler.
ActionHandler contains the basic act() function required to handle incoming action requests, although this can be overridden in subclasses to implement different behavior. In <tt>Controller</tt>, when a <tt>RewriteRule</tt>'s <tt>parse_regex</tt> matches the requested URL, it creates an instance of the <tt>RewriteRule</tt>'s indicated <tt>handler</tt> class, and calls <tt>->act()</tt> on that object, passing the <tt>RewriteRule</tt>'s <tt>action</tt> as a parameter.
==== Classes that extend ActionHandler ====
* [http://wiki.habariproject.org/en/Classes/AdminHandler AdminHandler]
* [http://wiki.habariproject.org/en/Classes/AjaxHandler AjaxHandler]
* [http://wiki.habariproject.org/en/Classes/AtomHandler AtomHandler]
* [http://wiki.habariproject.org/en/Classes/CronTab CronTab]
* [http://wiki.habariproject.org/en/Classes/InstallHandler InstallHandler]
* [http://wiki.habariproject.org/en/Classes/FeedbackHandler FeedbackHandler]
* [http://wiki.habariproject.org/en/Classes/ThemeHandler ThemeHandler]
* [http://wiki.habariproject.org/en/Classes/UserHandler UserHandler]
* [http://wiki.habariproject.org/en/Classes/UserThemeHandler UserThemeHandler]
* [http://wiki.habariproject.org/en/Classes/XMLRPCServer XMLRPCServer]
===[http://wiki.habariproject.org/en/Classes/ArrayObject ArrayObject] (built in class)===
Many objects in '''Habari''' are arrays of other objects. In addition to being able to iterate over the members of the array, it is also often useful to perform some actions on the entire collection of member objects. For these situations, PHP's built-in ArrayObject is extended. The following classes all extend ArrayObject:
* [http://wiki.habariproject.org/en/Classes/Comments Comments]
* [http://wiki.habariproject.org/en/Classes/EventLog EventLog]
* [http://wiki.habariproject.org/en/Classes/InfoRecords InfoRecords]
** [http://wiki.habariproject.org/en/Classes/CommentInfo CommentInfo]
** [http://wiki.habariproject.org/en/Classes/InfoObject InfoObject]
** [http://wiki.habariproject.org/en/Classes/PostInfo PostInfo]
** [http://wiki.habariproject.org/en/Classes/UserInfo UserInfo]
* [http://wiki.habariproject.org/en/Classes/Posts Posts]
* [http://wiki.habariproject.org/en/Classes/RewriteRules RewriteRules]
* [http://wiki.habariproject.org/en/Classes/Tags Tags]
* [http://wiki.habariproject.org/en/Classes/UserGroups UserGroups]
* [http://wiki.habariproject.org/en/Classes/Users Users]
===[http://wiki.habariproject.org/en/Classes/Bitmask Bitmask]===
===[http://wiki.habariproject.org/en/Classes/Cache Cache]===
* [http://wiki.habariproject.org/en/Classes/FileCache FileCache]
===http://wiki.habariproject.org/en/[Classes/ColorUtils ColorUtils]===
===[http://wiki.habariproject.org/en/Classes/CronJob CronJob]===
===[http://wiki.habariproject.org/en/Classes/CronTab CronTab]===
===[http://wiki.habariproject.org/en/Classes/DatabaseConnection DatabaseConnection]===
===[http://wiki.habariproject.org/en/Classes/Error Error]===
===[http://wiki.habariproject.org/en/Classes/Exception Exception]===
* [http://wiki.habariproject.org/en/Classes/Error Error]
* [http://wiki.habariproject.org/en/Classes/XMLRPCException XMLRPCException]
===[http://wiki.habariproject.org/en/Classes/Format Format]===
===[http://wiki.habariproject.org/en/Classes/FormControl FormControl]===
* [http://wiki.habariproject.org/en/Classes/FormControlCheckbox FormControlCheckbox]
* [http://wiki.habariproject.org/en/Classes/FormControlPassword FormControlPassword]
* [http://wiki.habariproject.org/en/Classes/FormControlSelect FormControlSelect]
* [http://wiki.habariproject.org/en/Classes/FormControlStatic FormControlStatic]
* [http://wiki.habariproject.org/en/Classes/FormControlText FormControlText]
* [http://wiki.habariproject.org/en/Classes/FormControlTextArea FormControlTextArea]
* [http://wiki.habariproject.org/en/Classes/FormControlTextMulti FormControlTextMulti]
===[http://wiki.habariproject.org/en/Classes/FormUI FormUI]===
===[http://wiki.habariproject.org/en/Classes/HTMLTokenizer HTMLTokenizer]===
===[http://wiki.habariproject.org/en/Interfaces/Importer Importer]===
===[http://wiki.habariproject.org/en/Classes/InputFilter InputFilter]===
===[http://wiki.habariproject.org/en/Classes/Locale Locale]===
===[http://wiki.habariproject.org/en/Classes/Media Media]===
===[http://wiki.habariproject.org/en/Classes/MediaAsset MediaAsset]===
===[http://wiki.habariproject.org/en/Interfaces/MediaSilo MediaSilo]===
===[http://wiki.habariproject.org/en/Classes/Miscellaneous Miscellaneous]===
===[http://wiki.habariproject.org/en/Classes/Pluggable Pluggable]===
* [http://wiki.habariproject.org/en/Classes/Plugin Plugin]
* [http://wiki.habariproject.org/en/Classes/Theme Theme]
===[http://wiki.habariproject.org/en/Classes/Plugins Plugins]===
===[http://wiki.habariproject.org/en/Classes/QueryProfile QueryProfile]===
===[[QueryRecord]]===
The QueryRecord class defines a base object that corresponds to a database table. It has specific methods for inserting, updating, and deleting an instance of itself from the database.
==== Classes that extend QueryRecord ====
* [http://wiki.habariproject.org/en/Classes/CronJob CronJob]
* [http://wiki.habariproject.org/en/Classes/Comment Comment]
* [http://wiki.habariproject.org/en/Classes/File File]
* [http://wiki.habariproject.org/en/Classes/LogEntry LogEntry]
* [http://wiki.habariproject.org/en/Classes/Post Post]
* [http://wiki.habariproject.org/en/Classes/RewriteRule RewriteRule]
* [http://wiki.habariproject.org/en/Classes/User User]
* [http://wiki.habariproject.org/en/Classes/UserGroup UserGroup]
===[[Classes/RemoteRequest RemoteRequest]]===
===[http://wiki.habariproject.org/en/Interfaces/RequestProcessor RequestProcessor]===
* [http://wiki.habariproject.org/en/Classes/CURLRequestProcessor CURLRequestProcessor]
* [http://wiki.habariproject.org/en/Classes/SocketRequestProcessor SocketRequestProcessor]
===[http://wiki.habariproject.org/en/Classes/RPCClient RPCClient]===
===[http://wiki.habariproject.org/en/Classes/Session Session]===
The '''session''' class registers a [http://us3.php.net/manual/en/ref.session.php PHP session] handler to store transient data between page loads.
===[http://wiki.habariproject.org/en/Classes/Singleton Singleton]===
A [http://en.wikipedia.org/wiki/Singleton_pattern singleton] is a [http://en.wikipedia.org/wiki/Design_pattern_%28computer_science%29 design pattern] used to restrict a class from being instantiated more than once. '''Habari''' uses the singleton pattern for a number of its classes. For example, the Controller class, which implements the Controller in Model-View-Controller, is a singleton because there can be only one Controller driving the Model-View. The DB class is a singleton facade: the normal execution of '''Habari''' requires only a single database instance for execution, but there are situations in which one might want to connect to a different database without having to write your own database handling routines.
* [http://wiki.habariproject.org/en/Classes/Controller Controller]
* [http://wiki.habariproject.org/en/Classes/DB DB]
* [http://wiki.habariproject.org/en/Classes/Options Options]
* [http://wiki.habariproject.org/en/Classes/Update Update]
* [http://wiki.habariproject.org/en/Classes/URL URL]
===[http://wiki.habariproject.org/en/Classes/Site Site]===
The '''site''' class is responsible for building URLs and filesystem paths of various sorts for the current site. This class provides a unified mechanism for building URLs and paths that supports Habari's multi-site functionality.
===[[Stack Class]]===
===[http://wiki.habariproject.org/en/Classes/TemplateEngine TemplateEngine]===
TemplateEngine defines the mechanics of a specific templating system. The two currently defined engines are RawPHPEngine and Smarty. Using the RawPHPEngine, one will construct template files in a manner similar to that used by, for example, WordPress: you mix HTML and PHP code together. Using the SmartyEngine, one will construct [http://smarty.php.net/ Smarty] template files.
* [http://wiki.habariproject.org/en/Classes/RawPHPEngine RawPHPEngine]
* [http://wiki.habariproject.org/en/Classes/SmartyEngine SmartyEngine]
===[http://wiki.habariproject.org/en/Classes/Themes Themes]===
===[[http://wiki.habariproject.org/en/nterfaces/URLProperties URLProperties]===
===[http://wiki.habariproject.org/en/Classes/Utils Utils]===
The '''utils''' class is a bit of a grab-bag of handy routines that don't really fit well within other classes.
===[http://wiki.habariproject.org/en/Classes/UUID UUID]===
The '''uuid''' class provides a mechanism for producing "universally unique identifiers", per [http://www.ietf.org/rfc/rfc4122.txt RFC 4122].
===[http://wiki.habariproject.org/en/Classes/Version Version]===
The '''version''' class is a small class used to record metadata about the current Habari version. Specifically, it reports the Habari API and database version numbers.
===[http://wiki.habariproject.org/en/Classes/XMLRPCBinary XMLRPCBinary]===
===[http://wiki.habariproject.org/en/Classes/XMLRPCClient XMLRPCClient]===
===[http://wiki.habariproject.org/en/Classes/XMLRPCDate XMLRPCDate]===
===[http://wiki.habariproject.org/en/Classes/XMLRPCStruct XMLRPCStruct]===
===[http://wiki.habariproject.org/en/Classes/XMLRPCUtils XMLRPCUtils]===
Alphabetical
{{developer}}
[[Category:Manual]]
''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 [http://www.php.net/autoload __autoload()] 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]]
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.5, mileage will vary with older versions), and Serendipity 0.9 and up. Other importers are planned.
You must first activate the plugin for the platform from which you are importing 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.
=Importing A Wordpress Database=
After choosing to import a Wordpress database, you will 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 will 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, as will Raman Ng's [http://wiki.habariproject.org/en/Available_Plugins#Raman_Ng_.28a.k.a._tinyau.29 RN Custom Permalink] plugin. 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.
Note: Due to the feature of importing users, in earlier versions of Habari you could 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 0.4 release, and as of revision 1319 this issue was resolved.'''
=Importing A Serendipity Database=
After choosing to import a Serendipity database, you will be prompted to enter the version of Serendipity you are using, '''OR''' the location of the root web directory where the Serendipity configuration file can be found to gather version information. After entering this, you will be prompted for your database connection details, whether or not to import your Serendipity categories as tags, and whether or not to import unapproved comments. Enter these details, then click the '''Process with Import''' button to procede.
As with Wordpress, your permalinks and paths will be lost. Explore the same options discussed above to maintain your previous links.
[[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
**PostgresSQL
*PHP version 5.2 or above with the following modules enabled
**PHP Data Objects (PDO) and the PDO driver for the database you wish to use
**SimpleXML
**Hash
**Iconv
**Mbstring
**Tokenizer
**JSON
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 patches]]</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.
====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>
'''NOTE''': the password elements accept either plaintext or encrypted passwords. We strongly recommend that you never store a plaintext password in your <tt>config.php</tt> file. You may use our [http://www.habariproject.org/utils/ssha512.php SSHA512 password encryption page] to generate an encrypted password.
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 [http://wiki.habariproject.org/en/Troubleshooting troubleshooting] page for more details...
==Special Instructions==
===Software Bundles===
*[http://wiki.habariproject.org/en/Installation/Special_Instructions/WAMP WAMP]
*[http://wiki.habariproject.org/en/Installation/Special_Instructions/Nginx Nginx]
*[http://wiki.habariproject.org/en/Installation/Special_Instructions/LiteSpeed LiteSpeed]
===Hosting Solutions===
*[http://wiki.habariproject.org/en/Installation/Special_Instructions/A_Small_Orange A Small Orange]
*[http://wiki.habariproject.org/en/Installation/Special_Instructions/Dreamhost Dreamhost]
*[http://wiki.habariproject.org/en/Installation/Special_Instructions/Bluehost Bluehost]
[[Category:Manual]]
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 Introduction]]
To assist in the management of media, such as images, video and audio, Habari provides a mechanism for defining virtual filesystems for media, called Media Silos. A silo provides a consistent interface for dealing with media. Examples of media silos can be seen in the [http://wiki.habariproject.org/en/SimpleFileSilo SimpleFileSilo] or FlickrSilo plugins. A developer wanting to interface with a media service should write a plugin that implements the MediaSilo interface.
== Media Overview ==
The Habari media system provides access to disparate systems through a unified interface. This is done through use of a virtual filesystem. Each path in the virtual filesystem maps to a specific media asset, be it a directory or file. The root directory in a virtual path indicates the Silo in which the asset exists.
A "Silo" is a wrapper for media from a specific source that provides a standard set of functions for access. The name of the silo is the root directory in the filesystem.
For example, if the Silo is "Local Files", then all of the assets within that silo will have a path that begins with "/Local Files". The Silo is responsible for mapping virtual paths within its root to actual files.
The meaning of "virtual filesystem" is that a Silo can present any path that easily allows access to an asset, without representing the physical location of that asset. A file located in the virtual path "/Flickr/tags/Monkey" might actually be located on a different server, and might not even use the name specified as the filename. By using the virtual filesystem, Habari can present a unified interface across all Silos and their asset repositories that is easily navigable via a traditional path structure.
In addition to specifying the structure of the virtual paths within their purviews, a Silo provides access to the local and remote physical assets it contains. Functions in the Silo provide access to the metadata used to address the file via the web - often a URL, but sometimes something more abstract. It should also be possible to directly access the physical file of the asset for manipulation by the system via plugin.
Silos provide functions to enumerate assets stored in a specific virtual path. Also, Silos provide permission information on the assets, and can even allow the copying, deleting, and uploading of new assets, regardless of whether the store is local or remote.
Silos are simply an abstraction for the Media API, and do not provide an interface of their own.
Rather than interacting with Silos directly, Habari provides a Media class that functions as a [http://en.wikipedia.org/wiki/Fa%C3%A7ade_pattern facade pattern]. Using path names that include the silo root names, it dispatches functions to the correct Silo and returns its results without the consumer of the API needing to know anything about the virtual paths or the Silos that implement them.
== Usage in Habari ==
The Publish page of the Habari Admin contains an area that allows a user to select assets for insertion into posts. The assets are supplied via the Media/Silo system.
== Consuming Silo Data ==
== Creating New Silos ==
To create a new media silo, you need to override the methods defined in the MediaSilo interface. Below is a description of each of the methods.
=== silo_info() ===
This method returns information about the silo. Most importantly, it should return the name of the silo, which is used as the first part of the media silo's URL. For example, the Flickr media silo would return an associative array with key <tt>name</tt> that has the value <tt>flickr</tt>. Therefore, all Flickr media assets would be accessed through a virtual directory beginning with <tt>flickr/</tt>. An icon can optionally be defined in the array with a key of <tt>icon</tt> and the URL of the icon which should be used. These icons are displayed on the publish page and may become the only representation of a silo. The icon should be 16 pixels by 16 pixels.
=== silo_dir( $path ) ===
This method returns an array of MediaAsset objects that are located at the given virtual path. It should map the virtual path to the real path of the service, creating MediaAssets that contain the correct virtual paths.
=== silo_get( $path, $qualities = null ) ===
=== silo_put( $path, $filedata ) ===
=== silo_delete( $path ) ===
=== silo_highlights() ===
=== silo_permissions( $path ) ===
===Custom Media Types===
Every MediaAsset has a "type", similar to a MIME type, which is used to control how MediaAssets are output. Types can be silo-specific, and can be added by silo plugins, so that the way that assets are previewed or output can be customised. The <tt>media</tt> object in the <tt>habari</tt> JavaScript object has functions named after the MediaAsset types. If a function exists with the MediaAsset type name, that function is called to output the asset. If no function exist with that type name, the default "image" output function is used.
To add a custom type, you need to specify the type when creating the MediaAsset object, and add the custom output to the JavaScript object, as demonstrated below for the Flickr MediaSilo.
<code lang="php">
// set other properties
$props['filetype'] = 'flickr';
// Add a new media asset
$results[] = new MediaAsset(
self::SILO_NAME . '/photos/' . $photo['id'],
false,
$props
);
</code>
The Javascript can be added using the <code>action_admin_footer</code> hook.
<code lang="php">
public function action_admin_footer( $theme ) {
if ($theme->admin_page == 'publish') {
echo <<< FLICKR
<script type="text/javascript">
habari.media.output.flickr = function(fileindex, fileobj) {
habari.editor.insertSelection('<a href="' + fileobj.flickr_url + '"><img src="' + fileobj.url + '"></a>');
}
habari.media.preview.flickr = function(fileindex, fileobj) {
var stats = '';
return '<div class="mediatitle">' + fileobj.title + '</div><img src="' + fileobj.thumbnail_url + '"><div class="mediastats"> ' + stats + '</div>';
}
</script>
FLICKR;
}
}
</code>
{{developer}}
[[Category:Manual]]
/***
|''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 {{ #functionname: argument 1 | argument 2 | argument 3... }}
# Magic words and variables http://meta.wikimedia.org/wiki/Help:Magic_words eg {{{__TOC__}}}, {{CURRENTDAY}}, {{PAGENAME}}
# {{{^''}}} (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 {{ subst: templatename }}
***/
//{{{
// 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'
//}}}
Inside out, Habari is being designed to be modern, elegant and user friendly. The administrative interface is representative of this approach, and is meant to provide you with the path of least resistance to your goal, while still letting you retain fine-grained control.
Most of what is described in this document, by its very nature should be intuitive and self-explanatory. However, in an effort to maintain these hard-won properties at the benefit of all involved--users and developers alike--this document serves as a guide for working on or adding to Habari's administration interface.
Habari's compartmentalized approach to development dictates that the core of Habari contain only the most essential functionality, leaving bells and whistles to be provided through its powerful plugin architecture.
This mindset should be kept at the forefront of the interface design aspects of Habari as well.
For developers contributing to the core of Habari's administration interface, this means staying the path, and not giving in to the urge to force upon it, features it doesn't need.
For third-party developers, this means focusing on doing a few things great, rather than a lot of things averagely.
For everyone involved, it means honoring the spirit of Habari's powerful simplicity.
== General Guidelines ==
There are some basic rules that will help integrate most plugin option pages, new media silos and the like into the existing style of the interface:
* Retain Existing Practices
* Reuse Elements
* Less is More
Layout structurally, most elements, be it text or form elements, are held inside a centered column of containers. While this provides structure and segregation, it is generally a good rule of thumb for the elements themselves to imply the structure.
==== A Word on the Use of Colors ====
The majority of Habari's administration interface is kept in the neutral shades of grayscale, with colors being used only when truly needed, or at the very least when they distract the least. This is done to keep the administration interface 'genderless', and to promote the use of shapes and contrast as the primary aides in guiding the user.
Therefore, please consider with great care the addition of color to an element. Does the color serve a user-centric purpose? Does it enhance the use of the elements, or would it do just as well being grayscale? Does it call unwanted attention to itself, at the cost of other, perhaps more important elements?
Related to the use of colors is the use of flat, graded and pseudo-3D elements. Generally speaking, gradients aren't used for anything except to indicate shadows, leaving the vast majority of the elements in the Habari administration interface flat-shaded.
==== A Word on Blueprint CSS ====
The Habari administration interface makes use of a CSS framework called Blueprint CSS to encourage a more coherent and homogeneous look and feel. You can read more about Blueprint CSS and its many virtues elsewhere. Suffice it to say, that it is good practice to stay as much as possible within the confines of its column system whenever possible.
If you do need to lay out differently from what Blueprint CSS allows, consider if there exists a precedent for what you're trying to achieve, and consider adapting or reusing that solution instead, if possible.
==== A Word on Width ====
There are basically three widths in Habari's administration interface:
* '''Single column, 790px wide.'''
This is how most of Habari's administration interface is laid out, including the create, options and manage pages. Most of the content should be kept in individual containers, with new containers for each set of content (as per the Configure pages).
* '''Multiple column, xxxpx wide.'''
For pages like 'Activity', where the purpose of the page is to present the user with an overview of the site's activity, the data ´might be best presented in multiple columns, both to increase the data intake, but also to save the user from having to scroll, thus speeding up the time it takes to gain an overview.
* '''Full-width pagesplitter.
''' While using it for full-width elements isn't mandatory, the pagesplitter was designed specifically for this particular use, like for instance the timeline on the manage pages or the media silo plugins. A more in-depth description of its use and operation can be found later in this document.
As with everything else, it is suggested that you follow examples already set by the interface layout, if at all possible. And if that isn't possible, to at least stay in the 'spirit' of the layout.
== The Menu Bar ==
Habari's near-black menu bar stays fixed to the top of the browser window, regardless of scrolling, making it always available to the user.. It contains to its extreme left, the menu itself, which is described below. And to its extreme right, a link to and the name of, the site for which this is the administration interface.
=== The Menu ===
The function of The Menu isn't that different from that of the Windows Start button; namely to provide a single point of entry to the entirety of the Habari administration interface. As such any and all pages available in the administration interface are, and should be, available from this menu.
The Menu is a 'dropbutton', which serves both as a menu as well as a title for the page you are currently on. Dropbuttons are described in greater detail later in this document, but the basic gist is that upon hovering the dropbutton, a menu will reveal itself.
One thing differentiates The Menu from the rest of the dropbuttons in Habari; it has sub-menus. Also worth noting is that the parents of these sub-menus are themselves proxy links of the first item in the sub-menu. Because of this behavior, items in a sub-menu should obviously be directly related to the sub-menu's parent, with the most related and most likely to be used item, at the very top.
Aside from appearing as the label on the dropbutton itself, function thus as a title for the current page, the item for the current page is also marked in The Menu listing itself by a small 'V' mark, to differentiate it.
==== The 'Content/Admin' Menu Structure ====
All the available pages in the Habari administration interface fall into one of two categories:
'''Content:''' Create, Comments, Entries, Pages and Tags
Habari's raison d'être is the creation, publication and management of content. Whether it be entries, comments or tags.
'''Admin:''' Activity, Configure, Themes, Plugins, Import, Users and Logs
Anything not directly related to the creation and management of content is Admin, including the configuration of Habari itself, activating and deactivating themes or plugins, creating and administrating users.
At the bottom of The Menu, the item 'Logout' is to be found. It obviously falls outside of the category system.
==== Adding Custom Pages to the Menu ====
Not only does the addition of new pages to The Menu confuse the users, but The Menu itself can only hold so many items before it becomes confusing, and thus inefficient. Adding new pages to the administration page structure is therefore generally to be avoided unless absolutely necessary.
Habari has a framework in place which allows plugins and themes what essentially amounts to their own page, nestled in the confines of the Configure 'section' of the administration structure.
It is understood however, that some plugins might service the user better by being more readily available, for instance by having a place in the main menu itself. If this is the case, please consider with great care where the menu item best serves the user, rather than where it might get the most exposure.
Use the above section on the Content/Admin structure to determine where your page would work best, and whether or not it might best serve as in a sub-menu.
== Custom Controls ==
To help make Habari's administration interface as efficient as possible, a few custom controls have been created, the operation of which should be self-evident, but the use of which might require some guidance.
=== The Dropbutton ===
To conserve space and speed up the decision-to-action response time, a new control was devised, which operates as a cross between a dropdown menu and a button. Here's how it works:
* When inactive, the dropbutton has a the topmost item in its list as its label.
* Hovering the dropbutton will immediately reveal its list and allow it to be navigated.
* Clicking the dropbutton itself, will activate the topmost link.
* Clicking any link in the dropdown, will obviously activate said link.
When implementing a dropbutton, it is important to pay attention to the order of items in the list, in particular the first item, which will of course be the label of the button, and thus should be the most likely to be used, or at least the most descriptive for the collection of items in the list.
If a dropbutton has only a single item in it, it goes without saying that it won't have its dropmenu (and accompanying arrow).
If it is important for the user to recognize the presence of an item in the dropdown, and there is concern that he or she might overlook it, a separate one-item dropbutton can be placed next to the dropbutton with the individual item. This might be an 'update' item only available under certain circumstances, and which would in all likelihood require action when present.
A feature that needs some usability testing before it can be okay’d is for the dropbuttons, when used in repeating setups, like a list of comments, as on the Comments page, clicking an item in the list (except of course the topmost one), would change all the repeating dropbuttons to use that item as their topmost one. This might help when going through for instance a list of comments and marking individual comments as spam, that it would make the process faster, by adapting the interface to the current task being performed.
Dropbutton Implementation
While the dropbutton employs javascript to hold its menu open for a short while even after the mouse leaves the dropbutton, it works almost as well in a non-JS environment.
While the dropbutton was made to work as a fold-out list of actions, it can work just as well without a list, which may sometimes be a preferable way to present links that represent ‘actions’.
And finally, the dropbutton resizes itself horizontally to accomodate lengthier text, up to 120px. Anything larger than that gets cropped, or in the case of browsers supporting the text-overflow CSS property, cropped and appended with an ellipsis.
Its code looks like this:
<source lang="html4strict">
<div class="dropbutton">
<a href="#">Action #1</a>
<ul>
<li><a href="#">Action #2</a></li>
<li><a href="#">Action #3</a></li>
</ul>
</div>
</source>
For update buttons and other buttons of high importance, the ‘alert’ class can be applied to color the button orange-red, so as to be more visible.
=== Tabs and Pagesplitter ===
The purpose of the tabs, alongside the pagesplitter, is to hide from view sections of a page which are not essential to the basic operation of the page; thus minimizing the amount of clutter. These include advanced settings, media silos, timelines and the like.
Clicking an inactive tab opens its pagesplitter. Clicking an active tab closes its pagesplitter. Clicking an inactive tab when another tab is active closes the active tab and in turn opens the newly selected tab's pagesplitter.
A tab row can include any number of tabs, of which either none or only a single tab can be active at any given time. Up to an including seven tabs (each tab in a row being 100px wide) will display in a row, and beyond that, in a dropbutton, like so:
Tabs are bound to a Pagesplitter, so called because it 'splits' the page, revealing what is behind it. As only a single tab in a row of tabs can be active at any given time, it goes without saying that only a single Pagesplitter can be active at any given time, per tab row.
Multiple tab rows per page are possible, though it is strongly suggested that tab rows not be placed one after the other, without any other page elements in-between.
It is also strongly suggested that tabs be placed in relation to any other content they might be related to, as in the case of media silos being placed just prior to the content area on the Create pages, or the Timeline being placed after the main navigation area, but before the content area on the Comments page.
Also, due to their full-width nature, they need to be placed outside of any other containers.
A design for vertically-resizable pagesplitters has been suggested, but at the time of writing has not seen implementation.
=== The Timeline ===
The activity timeline is a combined poor-man's statistics graph as well as an indicator of where in the content-archive the currently viewed items belong.
In its default monthly view, the timeline graph basically consists of a number of 1-pixel wide columns, each representative of a single comment or entry. Thus, if one month has seen 100 comments, and another month saw 200 comments, they would be a 100px and 200px wide respectively, showing one month to have been twice as active as the other.
The 'loupe' is a transparent slider, which functions both as an indicator of the current position in the archives timeline, and at the same time as a control for moving around in those archives as well as setting the amount of items viewed. It is per default 20 pixels wide--thus showing 20 items of content--but can be resized in 20 pixel increments up to 100.
The loupe functions as a handle, which can be dragged and dropped on the slider that is the timeline, allowing the user to easily move his view to an approximate point in time.
Here is how the user can interact with it:
* Clicking and holding in the center of the loupe allows it to be dragged and dropped in 20-pixel increments on the timeline, which in turn will reload the results.
* The loupe can be resized in increments of 20 by dragging its sides.
* Clicking on the timeline, will shift the loupe its own length in the direction of the click.
* Double-clicking the timeline will shift the loupe directly to the double-clicked position.
Note: The following is a feature-suggestion only, pending testing of its implementation viability. Concerns are primarily bandwidth needed and the ensuing loading speeds.
Because each listed item corresponds directly to a 1-pixel-wide column in the timeline, elements on the page can interact with the timeline, to provide the user visually with valuable data. By hovering for instance a name, an IP or an entry name, the related items in the timeline highlight themselves, after which they slowly fade out again.
This allows for the user to hover the name of an entry on the Comments page, and have the density of comments for said entry highlighted on the timeline. On mouseout, the timeline items slowly fade out, allowing the user enough time to realign and/or resize the loupe to take a closer look.
=== The Tag Weight Graph ===
Tags themselves have no timestamp, as they are used at will, and so make no sense in a timeline. Tags however have a 'weight', indicative of how many entries they have been used with. On the Tags page, this weight is used to display a timeline-similar graph that can be similarly operated.
=== Inline Labels ===
As seen in particular on the Create pages, some form elements have had their label moved into their content area of their target form element. This was done to create as lean and trim a layout as possible, but comes at the cost of possible confusion on the part of the user once those elements have been filled out with user data, and the labels are no longer accessible.
As an aid, elements with inline labels have small 'glue on' labels attached to them on focus and hover.
When using inline labels, it is very important that to do so with the greatest care, and preferably as sparingly as possible. Here are a few guidelines:
* To the extent possible, elements by its size and position, should describe itself.
* The page should be used often enough that the user easily memorizes the inline labels, based on the sizes and positions of the elements.
* The page should preferably contain few, easily distinguishable elements.
Ideal pages for inline labels are the Login page and the Create pages. Using inline labels on options pages and the like is likely to only confuse the user, and should be avoided.
=== Resize Handles ===
Some elements, like textareas, benefit greatly from being resizable, to allow the user greater overview and a better use of their vertical (or in some cases horizontal) screen space. Wherever the resize handles are visible next to an element, they can be dragged and dropped, resizing the element in the process.
Sliders
A slider consists of a rail with a drag and droppable handle. Changing the position of the handle on the rail, affects the page in some fashion. One such use in the Habari administration interface is on the Manage pages (Comments, Entries, Pages), where a slider controls the amount of content being shown, from everything through excerpts to nothing.
The slider is a powerful and elegant tool when used correctly, and is particularly helpful when the user is called upon to type in values on a scale, like the hue or brightness of a color, or the size of text.
=== Livesearch ===
To speed up the process of filtering results, all search fields in Habari's administration interface use the near-instantaneous 'livesearch', which fetches the results relevant to your query at every keystroke.
=== Search Filtering ===
While not truly a custom control, search filtering is a sibling to livesearch, and is therefore included here.
Using a system similar to Google's advanced operators, the search fields in Habari's administration interface function almost as command lines for filtering content, particularly on the Manage pages. Instead of having a separate page for filtering out draft entries, a simple search for status:draft is made, and only draft entries are shown.
These advanced operators provide an extremely powerful method of filtering, offering the user full control over what is being fetched, and what isn't.
Similarly, all the applicable links on the Manage pages, like the name of a comment author or a date of publication, actually perform a search, which shows up in the search field, and which is subsequently editable by the user.
Similarly to Gmail and Google Reader, the hotkey for switching focus to the Search element is '/'.
For a full description of search filtering, you should refer to the [http://wiki.habariproject.org/en/SimpleFileSilo search filtering reference].
== The Dashboard ==
The dashboard is meant to provide the user with an overview of the current activity on, or relating to, the blog. To achieve this, the dashboard consists of a customizable module system in which the user can freely rearrange, add to or remove modules. Here follows a breakdown of the functionality and operation of the dashboard module system.
=== Frontend Functionality ===
The follow operations are possible for the user, preferably without ever reloading the page:
* Rearrange modules by dragging and dropping them.
* View and change options, like the link for a feed, by clicking a module’s options icon in the top-right corner of module.
* Remove module, from within its options.
* Select a module-type from a dropdown and add it.
=== Backend Functionality ===
The dashboard comes with a default set of basic modules, such as:
* Feed
* Latest Entries
* Latest Comments
* Moderation Queue
It should be possible to add new modules as standard Habari plugins.
Plugin-Modules should show up on the dashboard when activated.
The dashboard comes with a default setup of ‘Latest Entries’ (latest entries module), ‘Latest Comments’ (latest comments module) and ‘Habari Developer Blog’ (feed module).
== Code Guidelines ==
=== Percentage Widths ===
The CSS file for the admin (<tt>/system/admin/css/admin.css</tt>) contains a set of classes from <tt>.pct5</tt> to <tt>.pct100</tt>, which when applied to an element, will set that element to the relevant percentage width.
Whenever applicable and possible, the width used in the admin should be set up using these percentage widths, to make it as easy as possible to later revise widths if necessary. This could for instance be the case if a column-based system is found to be needed down the line.
=== Semantic Classes ===
When introducing new containers on pages in the administration interface, you should consider using classes that are descriptive of the content of said container, even if you yourself won't need them for styling purposes. Their precense might very well aid others in either styling or hooking into your output with javascript.
[[Category:Manual]]
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 Habari's <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 under user/sites 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.
'''Properly configured''' simply mean that all sites running off one habari installation are configured to point to the same directory. Assume you install '''Habari''' in /home/www and /home/www is configured to be your DocumentRoot. You want to run three sites off this one installation of Habari: ''www.mysite.org'', ''themes.mysite.org'', and ''www.example.org''.
Install ''www.mysite.org'' in the root of the installation by creating a config file in /home/www. This will be the default site.
Install ''themes.mysite.org'' by creating the directory /home/www/user/sites/themes.mysite.org, then place a config file in that directory for the site.
Install ''www.example.org'' by creating the directory /home/www/user/sites/www.example.org, then place a config file in that directory for the site.
When you configure each of these sites on your webserver, point all of them to /home/www by setting /home/www as their DocumentRoot. When a request is made for one of these sites, '''Habari''' will look in the directories in /user/sites for one that matches the name of the site being requested. If it finds a match, the config file in that directory will be used to load the site. If it isn't able to find a match, '''Habari''' will fall back to the config file located in in '''Habari's''' root directory - in this example, using the config file in /home/www.
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 ==
#'''DocumentRoot''' is the directory which web servers use to supply requested files on the internet. It is often named /htdocs or /www, but doesn't have to be. Any subdirectory on a file system can be set up as the DocumentRoot for a given domain.
# 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.
# Each site can use the type of database desired.
#Sub-directory sites do not correctly function at the moment. Independent domains and subdomains function very well.
#The safest way to name a directory in user/sites is to use the same name as the site it will be hosting.
# When installing a sub-site using SQLite as its database, if the config file just contains a database name, the database file will be created in '''Habari's''' root directory.
[[Category:Manual]]
<!--{{{-->
<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:
<pre>
<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> » {$pingback->content}";
?>
</p>
</li>
<?php endforeach; ?>
</ol>
</div>
<?php endif; ?>
</source>
</pre>
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]]
''The information available on this page are based on revision 1260.''
Habari plugins are divided into two types: '''actions''' and '''filters'''.
* Actions do something.
* Filters modify something.
The above distinction is a little imprecise: actions can modify things, since objects are passed by reference. But the intent of actions is to perform some task, and filters are designed to return some modified value.
For example, the filter '''post_insert_allow''' is used to determine if a specific post can be inserted. Plugins that filter against this hook must accept as their parameters a boolean value, and the post object that is to be inserted. If the plugin determines that the post should not be inserted, the filter should return a boolean false value.
Plugin hooks are (usually) invoked using the format '''class_method_extra'''. This makes it easy, when reading a plugin's source code, to see where in the core Habari code the plugin is being invoked.
Plugins connect to the Habari hooks using the format '''type_hookname''', where "type" is either '''action''' or '''filter''' and "hookname" is the hook name as described above. As an example, consider a plugin that prevents anyone from saving new posts after 10 PM. The plugin will be invoked by the '''post_insert_allow''' filter, so the plugin will contain a function called '''filter_post_insert_allow'''. When Habari prepares to insert a post, it will trigger the plugin hook, thereby invoking the plugin's filter. The plugin's filter would check the local time, and if it's after 10 PM the filter would return boolean false. The post insertion process of Habari would be short-circuited, and the post would not be saved.
== List of Plugin Hooks ==
=== Actions ===
<pre>
admin/footer.php: Plugins::act( 'admin_footer', $this );
admin/header.php: Plugins::act( 'admin_header', $this );
admin/login.php: Plugins::act( 'admin_header', $this );
admin/login.php: Plugins::act( 'admin_footer', $this );
admin/plugins.php: Plugins::act( 'plugin_ui', $configure, $action );
admin/user.php: Plugins::act( 'theme_admin_user', $user );
classes/actionhandler.php: Plugins::act( $before_action_method );
classes/actionhandler.php: Plugins::act( $after_action_method );
classes/adminhandler.php: Plugins::activate_plugin( $plugin );
classes/ajaxhandler.php: Plugins::act('ajax_' . $this->handler_vars['context'], $this);
classes/ajaxhandler.php: Plugins::act('auth_ajax_' . $this->handler_vars['context'], $this);
classes/atomhandler.php: Plugins::act('init_atom');
classes/comment.php: Plugins::act('comment_insert_before', $this);
classes/comment.php: Plugins::act('comment_update_' . $fieldname, $this, $this->$fieldname, $value );
classes/comment.php: Plugins::act('comment_insert_after', $this);
classes/comment.php: Plugins::act('comment_update_before', $this);
classes/comment.php: Plugins::act('comment_update_' . $fieldname, $this, $this->fields[$fieldname], $value);
classes/comment.php: Plugins::act('comment_update_after', $this);
classes/comment.php: Plugins::act('comment_delete_before', $this);
classes/comment.php: Plugins::act('comment_delete_after', $this);
classes/controller.php: Plugins::act('handler_' . Controller::instance()->action, Controller::get_handler_vars());
classes/plugins.php: Plugins::act('plugin_activation', $file); // For the plugin to install itself
classes/plugins.php: Plugins::act('plugin_activated', $file); // For other plugins to react to a plugin install
classes/plugins.php: Plugins::act('plugin_deactivation', $file); // For the plugin to uninstall itself
classes/plugins.php: Plugins::act('plugin_deactivated', $file); // For other plugins to react to a plugin uninstallation
classes/post.php: Plugins::act('post_insert_before', $this);
classes/post.php: Plugins::act('post_update_' . $fieldname, $this, ($this->id == 0) ? null : $value, $this->$fieldname );
classes/post.php: Plugins::act('post_status_' . self::status_name($this->status), $this, null );
classes/post.php: Plugins::act('post_insert_after', $this);
classes/post.php: Plugins::act('post_update_before', $this);
classes/post.php: Plugins::act('post_update_' . $fieldname, $this, $this->fields[$fieldname], $value );
classes/post.php: Plugins::act('post_status_' . self::status_name($this->newfields['status']), $this, $this->fields['status'] );
classes/post.php: Plugins::act('post_update_after', $this);
classes/post.php: Plugins::act('post_delete_before', $this);
classes/post.php: Plugins::act('post_delete_after', $this);
classes/post.php: Plugins::act('post_publish_before', $this);
classes/post.php: Plugins::act('post_publish_after', $this);
classes/theme.php: Plugins::act('add_template_vars', $this, Controller::get_handler()->handler_vars);
classes/theme.php: Plugins::act( 'template_header', $this );
classes/theme.php: Plugins::act( 'template_footer', $this );
classes/theme.php: Plugins::act('theme_action', $action, $this, $user_filters);
classes/themes.php: Plugins::act('init_theme');
classes/update.php: Plugins::act('update_check');
classes/user.php: Plugins::act('user_insert_before', $this);
classes/user.php: Plugins::act('user_insert_after', $this);
classes/user.php: Plugins::act('user_update_before', $this);
classes/user.php: Plugins::act('user_update_after', $this);
classes/user.php: Plugins::act('user_delete_before', $this);
classes/user.php: Plugins::act('user_delete_after', $this);
classes/user.php: Plugins::act( 'user_authenticate_successful', self::$identity );
classes/user.php: Plugins::act( 'user_authenticate_successful', self::$identity );
</pre>
===Filters===
<pre>
admin/dashboard.php: Plugins::filter( 'statistics_summary', $stats );
admin/import.php: Plugins::filter('import_names', $import_names);
admin/import.php: Plugins::filter('import_stage', '', @$_POST['importer'], @$_POST['stage'], @$_POST['step']);
admin/plugins.php: Plugins::filter( 'plugin_config', $plugin_actions, $plugin['plugin_id'] );
classes/adminhandler.php: Plugins::filter( 'admin_publish_list_post_statuses', $statuses );
classes/adminhandler.php: Plugins::filter( 'publish_controls', $controls, $post );
classes/adminhandler.php: Plugins::filter( 'adminhandler_post_user_fields', $fields );
classes/adminhandler.php: Plugins::filter( 'plugin_config', $plugin_actions, $plugin_id );
classes/adminhandler.php: Plugins::filter( 'adminhandler_post_loadplugins_main_menu', $mainmenus );
classes/atomhandler.php: Plugins::filter('rsd_api_list', $apis_list);
classes/atomhandler.php: Plugins::filter( 'rsd', $xml, $this->handler_vars );
classes/atomhandler.php: Plugins::filter( 'atom_introspection', $xml, $this->handler_vars );
classes/atomhandler.php: Plugins::filter( 'atom_get_comments', $xml, $params, $this->handler_vars );
classes/atomhandler.php: Plugins::filter( 'atom_get_entry', $xml, $slug, $this->handler_vars );
classes/atomhandler.php: Plugins::filter( 'atom_put_entry', $xml, $slug, $this->handler_vars );
classes/atomhandler.php: Plugins::filter( 'atom_get_collection_namespaces', $namespaces );
classes/atomhandler.php: Plugins::filter( 'atom_get_collection_content_type', $params['content_type'] );
classes/atomhandler.php: Plugins::filter( 'atom_get_collection', $xml, $params, $handler_vars );
classes/atomhandler.php: Plugins::filter( 'atom_post_collection', $xml, $this->handler_vars );
classes/comment.php: Plugins::filter('comment_insert_allow', $allow, $this);
classes/comment.php: Plugins::filter('comment_update_allow', $allow, $this);
classes/comment.php: Plugins::filter('comment_delete_allow', $allow, $this);
classes/comment.php: Plugins::filter( "comment_{$name}", $out, $this );
classes/comment.php: Plugins::filter( "comment_{$name}_{$filter}", $out, $this );
classes/controller.php: Plugins::filter('rewrite_request', $start_url);
classes/cronjob.php: Plugins::filter( $this->callback, $result, $paramarray );
classes/databaseconnection.php: Plugins::filter( 'query', $query, $args );
classes/databaseconnection.php: Plugins::filter( 'query_postprocess', $query, $args );
classes/feedbackhandler.php: Plugins::filter('spam_filter', $spam_rating, $comment, $this->handler_vars);
classes/formui.php: Plugins::filter($this->success_callback, $result, $this);
classes/formui.php: Plugins::filter($validator, $valid, $this->value);
classes/logentry.php: Plugins::filter( 'insert_logentry', $this );
classes/logentry.php: Plugins::filter( "logentry_{$name}", $out, $this );
classes/logentry.php: Plugins::filter( "logentry_{$name}_{$filter}", $out, $this );
classes/options.php: Plugins::filter('option_get_value', $option_value, $name);
classes/plugins.php: Plugins::filter('activate_plugin', $ok, $file);
classes/plugins.php: Plugins::filter('deactivate_plugin', $ok, $file);
classes/post.php: Plugins::filter('post_setslug', $value);
classes/post.php: Plugins::filter('post_insert_allow', $allow, $this);
classes/post.php: Plugins::filter('post_update_allow', $allow, $this);
classes/post.php: Plugins::filter('post_delete_allow', $allow, $this);
classes/post.php: Plugins::filter('post_publish_allow', $allow, $this);
classes/post.php: Plugins::filter( "post_{$name}", $out, $this );
classes/post.php: Plugins::filter( "post_{$name}_{$filter}", $out, $this );
classes/rawphpengine.php: Plugins::filter('include_template_file', $this->template_dir . $template . '.php', $template);
classes/remoterequest.php: Plugins::filter( 'remoterequest', $data, $url );
classes/rewriterule.php: Plugins::filter( 'rewrite_args', $args, $this->name );
classes/rewriterule.php: Plugins::filter( 'rewrite_args', $args, $this->name );
classes/rewriterules.php: Plugins::filter('default_rewrite_rules', $default_rules);
classes/rewriterules.php: Plugins::filter('rewrite_rules', $system_rules);
classes/session.php: Plugins::filter( 'session_read', $dodelete, $session, $session_id );
classes/session.php: Plugins::filter( 'sessions_clean', $sql, 'read', $args );
classes/session.php: Plugins::filter( 'sessions_clean', $sql, 'destroy', $args );
classes/session.php: Plugins::filter( 'sessions_clean', $sql, 'gc', $args );
classes/site.php: Plugins::filter( 'site_url_' . $name, $url );
classes/site.php: Plugins::filter( 'site_path_' . $name, $path );
classes/site.php: Plugins::filter( 'site_dir_' . $name, $path );
classes/stack.php: Plugins::filter( 'stack_out', $stack, $stack_name );
classes/theme.php: Plugins::filter( 'template_user_filters', $user_filters );
classes/theme.php: Plugins::filter( 'template_fallback', $fallback );
classes/user.php: Plugins::filter('user_insert_allow', $allow, $this);
classes/user.php: Plugins::filter('user_update_allow', $allow, $this);
classes/user.php: Plugins::filter('user_delete_allow', $allow, $this);
classes/user.php: Plugins::filter('user_authenticate', $user, $who, $pw);
classes/userthemehandler.php: Plugins::filter('theme_act_' . $action, $handled, $this->theme);
classes/utils.php: Plugins::filter('slugify', $slug, $string);
classes/xmlrpcexception.php: Plugins::filter( 'xmlrpcexception_get_message', _t('Unknown XMLRPC Exception'), $code );
classes/xmlrpcserver.php: Plugins::filter('xmlrpc_methods', $res);
</pre>
{{developer}}
[[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 [http://wiki.habariproject.org/en/Theme_Functions 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. This page describes how to manually add recent comments support by editing your theme. If you don't want to write code, there is a [http://www.habariproject.org/dist/plugins/recentcomments.zip Recent Comments plugin] available from the Habari Extras repository that lets you customize how many comments to display and how to display them.
There are just two steps to add recent comments to your blog.
First, you need to make an array of recent comments available in your theme template. Add this code to the <tt>theme.php</tt> file for your theme inside the <tt>add_template_vars</tt> function:
<code lang="php">
$this->assign('recent_comments',
Comments::get( array('limit'=>25, 'status'=>Comment::STATUS_APPROVED, 'orderby'=>'date DESC' ) ) );
</code>
If you are using the Pingback plugin, and want to remove those from your recent comments, you can add the <tt>type</tt> parameter when retrieving comments, like this:
<code lang="php">
$this->assign('recent_comments',
Comments::get( array('limit'=>25, 'status'=>Comment::STATUS_APPROVED,
'type'=>Comment::COMMENT, 'orderby'=>'date DESC' ) ) );
</code>
This tells Habari to store the 25 most recent approved comments into a variable called <tt>$recent_comments</tt> that will be accessible in your template. You could separately list most recent pingbacks with a modification of the code.
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>
This loops through each of the comment objects in the array of recent comments using a <tt>foreach</tt> loop and prints them. You can edit the HTML to change how the comments are displayed.
* <tt>$comment->name</tt> is the name of the commenter.
* <tt>$comment->url</tt> is the URL of the commenter, such as their web site.
* <tt>$comment->post</tt> is the post object on which the commenter commented.
* <tt>$comment->post->title</tt> is the title of the post to which the comment applies.
You could even use <tt>$comment->content</tt> to output the content of the comment, if you wanted.
[[Category:Manual]]
[[Category:Theming]]
==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:
<pre> /archives/(?P<year>\d{4})</pre>
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:
<pre> archives/(?P<year>\d{4})/(?P<mon0>\d{2})/(?P<mday0>\d{2})/(?P<slug>[^/]+)/?$</pre>
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:
<code>
%archives/(?P<year>\d{4})/(?P<mon0>\d{2})/(?P<mday0>\d{2})/(?P<slug>[^/]+)/?$%i
</code>
==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:
<code>
archives/{$year}/{$mon0}/{$mday0}/{$slug}
</code>
$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:
<code>
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.'
);
</code>
==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:
<code>
$rule = RewriteRule::create_url_rule('"archives"/year/mon0/mday0/slug',
'UserThemeHandler', 'display_post');
$rule->insert();
</code>
{{developer}}
[[Category:Manual]]
[[Category:Outdated]]
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 non-use for session timeout period defined in php.ini.
==General==
===Staying Logged In===
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 workstations, 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.
==SQLite==
Unlike MySQL, which is a client-server database, SQLite is a simple file living in a directory on your web host. As such, it is subject to all the file permission rules that apply to any file on a computer's hard drive.
Habari attempts to improve the security of your SQLite installation by adding a ''Files'' section to your '''.htaccess''' file to deny access to your database by the web browsing public.
<pre>
<Files "habari.db">
Order deny,allow
deny from all
</Files>
</pre>
'''habari.db''' would be replaced with the actual name of your database.
As long as you leave your database somewhere in Habari's directory structure and don't rename it, this section will prevent the browser from directly accessing the database.
Other approaches you take to improving the security of an SQLite installation depends on whether your host runs PHP as a CGI process or as a module of the web server.
===PHP As A CGI Process===
You can check if PHP is being run as a CGI process by running the <tt>phpinfo()</tt> function on the server and looking for the <tt>SERVER_SOFTWARE</tt> entry. It will have something like "PHP-CGI" in the value string. If your host runs PHP as a CGI process, PHP normally has the same permissions that you as the site's owner have. It can read the files you can read. It can write to the files you can write to. Security is fairly good. You can do one of two things to make your database even more secure, though.
First, you can move your database file completely out of your public web directory. After you install Habari, copy your SQLite database file to another directory, for example, /home/user/sqlite. Then, change your config file to tell it where to find the database. When you install Habari, it creates your SQLite database in Habari's base directory. As a result, the connection string in your config file will say something like
<pre>'connection_string'=>'sqlite:habari.db'</pre>
where habari.db is the name of your SQLite database.
When you move your database, change this connection string to read
<pre>'connection_string'=>'sqlite:/home/user/sqlite/habari.db'</pre>
If you do move your database, you can create an '''.htaccess''' file in the directory to which you move your database file, and in it place a Files directive similar to the one Habari created when it was first installed:
<pre>
<Files habari.db>
Order deny,allow
deny from all
</Files>
</pre>
Replace habari.db with the actual name of your database file. This will keep visitors to your site from directly accessing your database file.
===PHP As A Server Module===
If your host runs PHP as a module to Apache, security is more difficult, and it is impossible to make an SQLite installation as secure as the previous situation unless the server is running mod_suphp, something over which those on shared hosts have no control.
The reason for this is that SQLite requires the directory in which the database resides to be writable by PHP. When PHP is run as a module, it generally doesn't belong to the same group that you as the site's owner belongs to, and won't have the same permissions as you. SQLite needs to be able to create a journal file in the directory in which the database file exists, so if something happens to the server your data won't be lost. As a result, you have to make the database's directory writable by the world. To minimize the security risks that this entails, you can move your database file into it's own directory and make only that directory world writable by giving it the permission 666, leaving all other directories in your public web structure at their usual permissions. Change the connection string in your config file to look for your database file in the proper place. For example, if you move the database file to
<pre>/www/sqlite/habari.db</pre>
your connection string would read
<pre>'connection_string'=>'sqlite:/www/sqlite/habari.db'</pre>
In addition, you can create an '''.htaccess''' file in the directory to which you move your database file, and in it place a Files directive similar to the one Habari created when it was first installed:
<pre>
<Files habari.db>
Order deny,allow
deny from all
</Files>
</pre>
Replace habari.db with the actual name of your database file. This will keep visitors to your site from directly accessing your database file.
[[Category:Manual]]
<<tabs txtMainTab "All" "All tiddlers" TabAll "Timeline" "Timeline" TabTimeline "Tags" "All tags" TabTags "More" "More lists" TabMore>>
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><pre>
function myformatter($text){
return strrev($text)
}
Stack::out('stack_name', 'myformatter');
</pre></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;}
/*}}}*/
==Using Subversion and applying patches==
[http://svnbook.red-bean.com/ Subversion] is a source code versioning system that allows developers to concurrently make changes to Habari's source code and reconcile any differences before a release is deployed.
Habari users have access to the current source code as being worked on by developers. Only those in the [http://wiki.habariproject.org/en/PMC PMC] have commit access and can commit changes back into the Habari subversion repository, but the repository is open to checkout for anonymous users.
To check out a copy of subversion to the current directory using the command line:
<pre>svn checkout http://svn.habariproject.org/habari/trunk/ .</pre>
To update an existing working copy to the latest code in the repository, execute this from the command line in the directory containing the working copy:
<pre>svn update</pre>
There are a number of graphical interfaces to interact with subversion for each operating system. These may be easier to use if you are not as familiar with running commands from the command line.
==Testing patches that are not yet released==
===What are diff files ?===
A diff file is the difference between a version of the source code in the repository and a working copy. It represents any changes, such as additional features or bug fixes, that have been made by a developer in a working copy. A diff file is generated using the <tt>svn diff</tt> command.
<pre>svn diff > descriptive_name_of_patch.diff</pre>
===Applying diff files to your copy of habari===
You can use the <tt>patch</tt> command to apply the diff file.
For windows systems, you can obtain a copy of <tt>patch</tt> from the [http://gnuwin32.sourceforge.net/packages/patch.htm gnuwin32 project]
The common execution is <code>patch -p0 < /path/to/file</code>. This command will apply the differences that have been recorded in <tt>file</tt> to the appropriate file(s) in the current directory. As one diff file can contain changes for many files, and it preserves relative paths to sub-directories, the format of the diff file may affect where you need to invoke patch.
===A practical example of diff and patch===
''predominantly from Scott Merrill's answer to Michael Bishop's question....''
I modified a whole bunch of files for the Site class. When I was done editing, I changed to the htdocs directory of my checkout:
<pre>$ cd /home/skippy/code/habari/trunk/htdocs</pre>
I then executed <code>svn status</code> to see (and review) a list of all the files in my local copy that I have changed:
<pre>$ svn status</pre>
After double-checking the list of files, I executed <code>svn diff</code> to output a list of all the changes to all the files. I piped this output into a file:
<pre>$ svn diff > /tmp/site.diff</pre>
I attached the diff file to an email, or upload it to the Google issue tracker.
You read my email, and save my diff file.
You transfer this file to your web host, which is running an SVN checkout of Habari.
Because my diff was made from <tt>/trunk/htdocs</tt>, you should change to that directory in your local copy. (You may need to discern this from the contents of the diff file, if you don't know otherwise.)
Once in the target directory, you execute:
<pre>$ patch -p0 < /path/to/site.diff</pre>
The output of patch should report what it's doing:
<pre>skippy@skippy:~/code/habari/trunk/htdocs$ patch -p0 < /tmp/site.diff
patching file system/admin/footer.php
patching file system/admin/content.php
patching file system/admin/dashboard.php
patching file system/admin/header.php
patching file system/classes/url.php
patching file system/classes/site.php
patching file system/classes/plugins.php
patching file system/classes/feedbackhandler.php
patching file system/classes/post.php
patching file system/classes/options.php
patching file system/classes/installhandler.php
patching file system/classes/user.php
patching file system/classes/controller.php
patching file system/installer/db_setup.php
patching file index.php
patching file user/themes/k2/comments.php
patching file user/themes/k2/header.php</pre>
If there are any problems, patch will report which file(s) failed. Figuring out what failed for each file, and why, can sometimes be challenging.
When you've determined that the patch works (or not!), and you want to go back to your vanilla Habari, you use the Subversion <tt>revert</tt> command. Using the "-R" (recursive) flag lets you easily revert your entire checkout to a clean state:
<pre>skippy@skippy:~/code/habari/trunk/htdocs$ svn -R revert *
Skipped 'config.php'
Reverted 'index.php'
Reverted 'system/admin/footer.php'
Reverted 'system/admin/content.php'
Reverted 'system/admin/dashboard.php'
Reverted 'system/admin/header.php'
Reverted 'system/classes/url.php'
Reverted 'system/classes/site.php'
Reverted 'system/classes/plugins.php'
Reverted 'system/classes/feedbackhandler.php'
Reverted 'system/classes/post.php'
Reverted 'system/classes/options.php'
Reverted 'system/classes/installhandler.php'
Reverted 'system/classes/user.php'
Reverted 'system/classes/controller.php'
Reverted 'system/installer/db_setup.php'
Reverted 'user/themes/k2/comments.php'
Reverted 'user/themes/k2/header.php' </pre>
==Resources==
[http://subversion.tigris.org/project_packages.html Download Subversion]
[http://gnuwin32.sourceforge.net/packages/patch.htm patch program for windows]
[http://svnbook.org/ Subversion Book]
[[Category:Manual]]
=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 [http://wiki.habariproject.org/en/Installation#Special_Instructions 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 [http://wiki.habariproject.org/en/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]]
=Before You Upgrade=
Before making any major change in your Habari installation, it would be prudent to create a backup of your database and your installation, in particular all the files in your Habari /user directory. The /user directory should contain all files which you have customized - plugins you have installed, themes you have installed, and class files you may have modified. The only exception to the Habari /user directory containing all custom files is if you are not using a locale that is distributed with Habari. In this case, you will have placed your language file in the /system/locale directory.
=The Upgrade Process=
#Download the new version of Habari that you will be installing. Unzip it into the location of your choice.
#Deactivate your active plugins. Some of the system plugins may have been changed. Some of the plugins you got in other places may not be compatible with the new version of Habari.
#Delete the following directories
##/3rdparty
##/scripts
##/doc
##/system (Unless you have installed a national language file. In that case delete everything from /system except for /system/locale/''name of your language''.
#Delete the index.php, LICENSE, and NOTICE files from the root of your installation. Do '''not''' delete your config.php file if it is in the root of your Habari installation, or the .htaccess file.
#Copy the new version of Habari to the same location as the old version.
#Using your web browser, navigate to your site. If there are any database changes that need to be made, they will be made automatically at this time.
#Log into your Habari administrative area. Navigate to the plugins page and reactivate the plugins one by one.
#Celebrate and enjoy the new features of your latest version of Habari!
=Version Specific Instructions=
==Upgrading from Version 0.4.1 To Version 0.5==
Many changes have been incorporated into version 0.5. For details of the changes that have been made see the [http://wiki.habariproject.org/en/Releases/0.5 release notes].
In particular, upgrade functions have been incorporated into the install handler, so when you upgrade your Habari installation, any required base database changes will be automatically handled without user intervention. However, the following are issues to note:
*The first page load after upgrading may be slow due to the conversion of the database to use UTF-8.
*Version 0.5 incorporates updates to Habari's [http://wiki.habariproject.org/en/Classes/FormUI form creation system] that changes how the options for plugins are stored. This means that you will need to reconfigure any of your plugins that require configuration for them to work properly.
*The location of the filecache has been moved from '''/system/cache''' to '''/user/cache'''. If your /user directory is not writable by the web server, you will need to create the /user/cache directory by hand and set it to be writable by the web server.
==[http://wiki.habariproject.org/en/Releases/0.4/Upgrading Upgrading From Version 0.3.3 to 0.4]==
==Upgrading From Version 0.2 To Version 0.3==
===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.
'''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).'''
===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.''
#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=
*[[User Manual]]
*[[Desktop Blogging]]
=Extending and Customizing=
*[[Themes]]: how themers can use '''Habari'''
*[[Plugins]]: User documentation for Plugins
[[Category:Manual]]
== Introduction ==
Welcome to Habari, the next-generation online publishing platform!
Habari was built from the ground-up with a firm understanding of the current state of blogging, and with a strong eye toward the future of internet publishing. Habari strives to make it as easy as possible to publish your content. It stays out of your way as much as it can, and lends a hand where it's needed.
The word "Habari" is Swahili for "news", as in "What's the news?" We feel this is an appropriate name for a blogging package not just because of the clever tagline, but because blogs are so often used to tell others what's the news in your life or your business.
As a Free Software project, Habari's most valuable asset is the community of users and developers who collaborate to improve the product. Habari is built by people just like you: folks with an interest in blogging and internet publishing.
== Installation ==
Habari has a few [http://wiki.habariproject.org/en/Installation#Server_Requirements Server Requirements], which you will need to meet to be able to run Habari. If you have what is required, (or better), you are good to go! 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.
To install the latest stable version of Habari:
*Download http://habariproject.org/en/download and unzip the Habari package.
*If you are using MySQL or PostgreSQL, obtain the connection settings needed below, or create a database for Habari on your web server.
*Upload Habari files on your web server.
*Point your web browser at http://example.com/ to begin installation with the Database Setup.
Depending on the Database Type you set, you will need to enter the following information:
You can click on the question mark at the top of the page to reveal more information on each field.
*Database Type: You can choose between MySQL, PostgreSQL, or SQLite. Your hosting company should be able to tell you what you should use.
*Database Host: Again your hosting company should have provided this information when you first joined.
*Username: This is the username used to connect Habari to the MySQL database.
*Password: This is the password used to connect Habari to the MySQL database.
*Database Name: Again your hosting company should have provided this information when you first joined.
*Table Prefix: You will only need to change this if you have more than one version of Habari installed.
The Site Configuration settings you will need to set are:
*Site Name: The name of your blog.
*Username: The name or alias you wish to use to administer your blog. This should not be the same as the username for the database.
*Password: The password associated to the above Username. Enter this in the second box, just to make sure you get it right.
*Admin Email: This is the email address associated with the above Username. Habari will email you to let you know everything is working.
The End! Habari should now be installed.
There are full [[Installation|Installation Instructions]] available, including some specific configuration for several popular hosting companies.
If you are happy to use the current development version of Habari, there are full instructions on how to access the Habari subversion repository, however, for most normal users you will need to download the latest stable version of Habari.
== Using Habari ==
After you have installed Habari, go to http://example.com/admin/. You will be taken to a login page. Enter your user name and password to open up Habari's administration interface. You will be greeted by the Dashboard. This page provides a snapshot of useful information, such as the total number of posts and comments on your site, the number of posts you've made, and more. Later you can install plugins to add new modules to the dashboard, greatly expanding its usefulness.
The primary way you will interact with Habari is through the main menu. The main menu is accessed by placing your mouse at the top left of the window. The menu should automatically drop down, to reveal to you the available choices. The menu items users see will be controlled by the permissions assigned to them by the site administrator. Clicking on any menu item will take you to the corresponding page within the administrative interface. For details on each menu item, see the corresponding section of this manual.
It should be noted that the main menu can also be accessed by keyboard shortcuts. If you press the Q key on your keyboard, the menu should expand. Press the Q key again to hide the menu. When the menu is displayed, each menu item has a keyboard shortcut displayed to the right. Using the keyboard shortcuts you can quickly navigate through Habari. Want to create a new entry? Press Q, then the 1 key. Want to change your site's theme? Press Q, then T. Want to add a new user? Q U.
== Your First Post ==
You can create your first post by clicking on the main menu, then selecting "Create Entry".
You are now looking at the publish entry page, by default you have three fields into which to enter information.
<html><img src="create_entry.jpg" style="float: right; height: 300px; width: 300px; padding: 5px;" /></html>
*The Title box is the name of your entry, this can contain a single word, a sentence, numbers, or symbols producable by your keyboard. By default, the title you supply will be used to create the URL by which this post will be accessed.
*The Content box contains the main body of your entry. In here you can put almost anything imaginable, although currently you will have to manually enter HTML for URL links and formatting such as bold or underlining. Beneath the content box is a small handlebar, which you may click and drag to adjust the amount of space used by the box.
*The Tag box contains the 'tags', or free-form labels, you apply to your content. You can use as many different tags are you like to describe your entry. If you are writing a post about a recipe you want to try, you might tag the post with the key ingredients. This allows visitors to navigate your site by the tag words, to find other recipes that use the selected ingredient. See the '''tags''' section for a more thorough description of tags.
Pressing the Save button will save your work into the Habari database. The post will still be a Draft, which means it is not available for the public to read. You can edit and save drafts as many times as you like, to make sure you get the post just right before letting the world read it. When you are satisfied with your post, click the Publish button. This will make the post visible to the world on your front page, as well as in your [http://en.wikipedia.org/wiki/Atom_(standard) Atom] syndication feed. Congratulations, you've posted your first entry!
You also have two additional buttons between the Tag box and the Save/Publish buttons, these contain optional extras you may wish to use.
Beneath the Tags input box are two buttons, labeled Settings and Tags. Clicking on either of these opens a pagesplitter (so called because it splits the page).
*Settings:
**Content State: You may manually adjust the status of your post. Instead of clicking the Publish button, you may select "Published" from the content state drop-down, and then click the Save button. If you want to unpublish something -- that it, keep it in the Habari database but prevent readers from seeing it -- you may change a Published post to Draft and then click Save. Plugins may add new content statuses.
**Comments Allowed: This tick box determines whether or not your visitors can leave comments on this item. When the box is ticked comments are allowed. By default this is set to ticked. If you remove the tick and click Save, no new comments will be accepted on this item. Any existing comments will be preserved and displayed.
**Publication Time: This is the time your post is set to be published. This is normally set by Habari. When you first begin a post, Habari will set the publication time to the current time. When you publish the post for public viewing, Habari sets the publication time to the time you hit the Publish button. The only instance when you should change the publication time is when you wish to create a scheduled post. If you set the publication time to a date and time in the future, then click the Publish button, Habari will make that post available for public viewing at the time you indicated.
**Content Address: This box lets you set a different title of your entry. This is useful in case the main title of your entry contains unusual characters or is excessively long. The name you provide in this box will appear in the address window in your web browser, not the title of the entry.
*Tags:
**This provides a quick reference of all the tags used on your blog. This list will be populated by more tags as you use them. You can click on a tag in the tag window, and it will appear in the tag box above. You can also clear the currently used tags, using the ‘Clear’ button.
== Key Terms and Ideas ==
=== Content Types ===
As a tool for managing content, Habari defines two default types of content.
*'''Entries''' are the most commonly used content type. These are the content type you use when creating a new blog post. They are displayed in reverse chronological order (newest first) on your home page and in your Atom syndication feed. You would use entries to post a review of the movie you watched yesterday, or what your weekend plans are.
*'''Pages''' are used for more static information in your site. Pages typically live outside of the date-based chronology of your site. You would use a Page to describe something that isn't associated with a specific date or time, like an "About The Author" biography.
Plugins have the ability to add additional content types.
'''NOTE''': When discussing Habari, the term "post" will frequently be used to mean any kind of content. If the content type matters for documentation or example purposes, a specific content type will be used. Entries and Pages are both Posts.
=== Content Statuses ===
Out of the box Habari maintains three post statuses - '''draft''', '''published''', and '''scheduled'''. Plugins have the option of adding further statuses to this list. The Undelete plugin that comes with Habari, for example, adds the '''deleted''' status.
*'''Draft'''. Drafts are unfinished, unpublished works. Drafts are not visible to regular visitors of your site. All new posts default to the "draft" status and remain that way until you explicitly change their status. As long as you don't publish the post, it will remain in this state so you can add to it, edit it, and change it as much as you want without being concerned that others will see it.
*'''Published'''. Published items are available for the entire world to read, and are included in your Atom syndication feed. As soon as you click the Publish button on Habari's post composition page, the status of the post is set to published. At this point it is viewable by the public at large. You can still edit the post.
*'''Scheduled'''. If you change a post's publication time to a future date, then click the Publish button, Habari sets its status to scheduled. The post will be visible to you when you are logged in to Habari's administrative section, but not to the general public. When the time you set for publication arrives, Habari will automatically change the status to published and make the post available for public viewing.
=== Tags ===
Tagging is a non-hierarchical way to categorize your content. When you create a post, you label it with one or more tags to show readers what other posts on your site it is related to. Tags have only a general semantic meaning, because the context of a label can change it's meaning, and this context isn't a part of the tag. Tags do, however, allow you to show the general categories of your work without forcing it into a hierarchical system of categories.
=== Comments ===
Readers can add feedback to published posts via '''comments'''. Habari requires all comments to be moderated before they are displayed on the site. Plugins can change this situations so that comments from frequent commenters are automatically approved, or comments left as spam are automatically marked as spam.
Out of the box Habari maintains three comment statuses - '''approved''','''unapproved''' and '''spam'''.
*'''Approved''' - These are comments that you have either manually approved for display on the site, or that have been filtered by a plugin and accepted for display on the site.
*'''Unapproved''' - All comments are unapproved when they are first made. It is up to you as the site administrator to either mark them for approval so they are displayed on the site, mark them as spam, or delete them.
*'''Spam''' - Spam comments are made in the hopes that someone will click through the links that they contain. Often spam is related to gambling or sexually oriented products, but can also be random comments that bear no relation to the post on which they are made. Spam is the reason that all comments are not automatically approved. As said above, you can install plugins in your Habari installation that filter all comments examining them for the characteristics of spam. If the comment fits the spam profile, it will either be automatically deleted or marked as spam and retained to further train the spam filtering plugin. It is strongly recommended that you install some kind of spam filtering plugin to save your time and to protect your site.
=== Feeds ===
The term '''feed''' indicates a stream of data which is "consumed" by a special application called a '''feed reader''' or '''aggregator'''. Your blog's feed contains the same data that is presented to visitors viewing your site, but it is composed in a way that is suitable for machine-to-machine communication, as opposed to machine-to-human communication. There are several formats for this machine-to-machine transmission, with the two most popular being [http://en.wikipedia.org/wiki/RSS_(file_format) RSS] and [http://atomenabled.org/ Atom]. Out of the box, Habari only speaks the Atom format.
RSS and Atom allow users to subscribe to your web site in their aggregator, like Bloglines or Google Reader. Their aggregator will periodically poll your website (usually once every couple of hours), checking to see if you've published any new content. If you have, your Habari installation will transmit that new content to the aggregator using the Atom format, and your posts will show up in the aggregator's list of new items.
Your main Atom feed can be located by appending <tt>/atom/1</tt> to your site's URL. Separate feeds are available for comments.
=== Themes ===
Themes allow you to customize the look and feel of the site for your readers. Habari includes several themes to choose from after installation, and many more are available from the community.
=== Plugins ===
By design, Habari by itself has a basic set of capabilities. Plugins add functionality, new capabilities, or additional content types to Habari. Habari includes several out of the box...
=== Loupe ===
<html><img src="timeline.jpg" style="height: 50%; width: 50%; padding: 5px; float: right;" /></html>
On certain administration pages, the loupe is a sliding, expandable window that operates on the timeline of a group of data. The default position for the loupe is the far right: the most recent items of the collection. The default width of the loupe encompasses 20 items. You can click inside the loupe and drag it left and right across the timeline: this will change the items shown beneath the loupe based on the date range highlighted by the loupe. On the left and right of the loupe are handlebars, which you can click to drag left or right to expand or shrink the size of the loupe: this will increase or decrease the number of items shown below.
The loupe makes it extremely easy to display and select specific timelines of data. You can drag and expand the loupe to display all the items from June of last year to June of this year. Or you can narrow the loupe to only display items from last month, or yesterday.
== Administration ==
=== The Dashboard ===
The Dashboard is the first page you see after you log in to Habari's administrative interface. The dashboard can be reached from any page of Habari's administrative interface by choosing '''Dashboard''' from the main menu, or by typing '''Q''' followed by '''D'''.
<html><img src="dashboard_with_modules.jpg" style="height: 300px; width: 300px; padding: 5px; float: right;" /></html>
The dashboard provides a summary of useful information for your site. The top section contains a couple of sentences telling you how long your site has been active, a series of high level statistics about your site, how many drafts you have awaiting completion, how many scheduled posts you have, and how many comments you have awaiting moderation.
Next is a series of '''modules'''. Modules are small sub-blocks that focus on specific details about your site. Habari comes with modules that contain lists of your recent published posts, recent comments on your blog, and recent activity that has been logged. Plugins can add various other modules dedicated to specific purposes. A plugin that redirects your feeds through the Feedburner service, for example, may use a module to let you know how many subscribers you have, while a plugin that tracks links to your site may use a module to show you the recent links.
Modules can be moved and rearranged, added, and hidden, so they stay in the positions that is most logical to you and show you the information you want to see. If you don't care about recent log activity, click the '''x''' in the upper left corner of the log module to hide it. If the first thing you want to see is any recent comments on your site, move the recent comments module to the top left of the modules section by clicking on the grey rectangle in the upper right of the module and dragging the module .
After all the visible modules, the dashboard has a dropdown list of all the modules that are available to show. To show a module you don't currently have displayed, choose the module then click the button with a + on it to the right of the list to display the module.
=== Managing Content ===
To manage your content -- draft or published, entry or page -- select one of the Manage links from the main menu. Habari creates a Manage menu item for each type of content in your system, providing a convenient way to access a specific type of content. The destination for each of these menu links is, in fact, the same page: the menus merely provide a shortcut to filter the display by the specified content type.
<html><img src="manage_posts.jpg" style="height: 300px; width: 300px; padding: 5px; float: right; " /></html>
Habari strives to make it as easy as possible to find and manage your content. The first thing to note when visiting the "Manage Content" page is the '''loupe'''.
Beneath the loupe are the posts returned by the current filters. Above the loupe are the filter controls, and additional navigation controls. By default, 20 posts will be presented beneath the loupe in reverse chronological order (newest first). If you have more than 20 items that satisfy the filters, you can page forward and backward to see the next or previous set of 20 posts using the "Older" and "Newer" links at the top left and right, respectively. To the right of the "Older" link is a report of the number of posts that match the current filter, and where your current display falls within that set.
In the top center of the Manage Content page is the filter box. You can use this box to type in search terms to find content. To find all posts that include the word "Habari", simply type "Habari" into the filter box and wait a moment. The list of posts below will be updated, and the quantity indicator to the left of the filter box will be updated. Beneath the filter box are several links that provide shortcut filter controls. To see only entries, click the "Entry" link: this will add <tt>type:entry</tt> into the filter box, restricting the list of posts to only those of type Entry. Click the "Entry" link again to remove that filter from the list. Using these buttons, you can quickly drill down to see all your content. Click the "Entry" link and the "Draft" link to see all your unpublished entries. Click the "Entry" link again to see all draft posts, regardless of content type.
Additional filters are available for manual entry. To see all posts authored by Scott, type "author:scott" into the filter box. There is no shortcut link for these other filters because it's possible that a single site might have, for example, a sufficiently large number of authors as to make the links dominate the screen.
You can combine the filter links with manual filter controls. To see all drafts about pizza, click the "Draft" button, and then type in "pizza". Or, manually type in "status:draft pizza".
The loupe will update for the current set of filtered posts. The combination of filter controls and loupe provide for extremely flexible content navigation.
Beneath the loupe is the list of posts that match the current filters. The post title, status, author, and date are displayed above a brief snippet of the post. This snippet should provide a bit of context about the post if you can't remember it from the title. The title of the post is a hyperlink, allowing you to click it to view that post. The status, author, and date are also hyperlinks that show you similar items (same status, same author, or same publication date). To the right of the post is a "drop button". This is a special control mechanism that acts like both a button and a drop-down list. If you place your mouse over the drop button it will expand to reveal all of the options available to you. The first item -- the one displayed before you placed your mouse over the drop button -- is the default action that will occur if you click your mouse. You may select one of the other items from the list, though, if you don't want to take the default action. When managing content, the usual values of the drop buttons will be "Delete" and "Edit".
To the left of the post is a checkbox. You can tick the boxes to mark one or more posts, allowing you to execute actions against groups of posts. The number of posts marked will be indicated at the bottom of the screen. The posts you select will be remembered, allowing you to page through all the posts that match the current filter. The number at the bottom of the screen will remind you that you have posts selected that might not be displayed on the current page. Next to the number of selected posts (which says "None selected" if you've not yet selected any) is another checkbox. If you tick this box, all posts that match the current filter will be selected. Note: this '''will''' select posts not displayed on the current page!
Once you have some group of posts selected, you can execute a single action against them. Select the action from the drop-down at the bottom of the page and then click the Submit button.
=== Comments ===
To manage your comments select the Comments link from the main menu.
As with posts, the first thing to note when visiting the Comments page is the loupe and the timeline at the top of the page. The loupe works with comments the same way as it does with posts.
<html><img src="manage_comments.jpg" style="height: 300px; width: 300px; padding: 5px; float: right;" /></html>
Beneath the loupe are the comments returned by the current filters. Above the loupe are the filter controls, and additional navigation controls. By default, 20 comments will be presented beneath the loupe in reverse chronological order (newest first). If you have more than 20 items that satisfy the filters, you can page forward and backward to see the next or previous set of 20 comments using the "Older" and "Newer" links at the top left and right, respectively. To the right of the "Older" link is a report of the number of comments that match the current filter, and where your current display falls within that set.
In the top center of the Comments page is the filter box. You can use this box to type in search terms to find comments. To find all comments that include the word ''Habari'', simply type ''Habari'' into the filter box and wait a moment. The list of comments will be updated, and the quantity indicator to the left of the filter box will be updated. Beneath the filter box are several links that provide shortcut filter controls to the different statuses and types a comment may have. To see only Approved comments, click the '''Approved''' link: this will add <tt>status:Approved</tt> to the filter box, restricting the list of comments to only those which are approved. Click the '''Approved''' link again to remove that filter from the list. Using these , you can quickly drill down to see all your comments. Click the '''Unapproved''' link and the '''Pingback''' link to see all your unapproved pingbacks. Click the '''Unapproved''' link again to see all pingbacks, regardless of status.
You can combine the filter links with manual filter controls. To see all approved comments containing the word ''terrific'', click the '''Approved''' link, and then type in ''terrific''. Or, manually type in '''status:Approved terrific'''.
The loupe will update for the current set of filtered posts. The combination of filter controls and loupe provide for extremely flexible content navigation.
Beneath the loupe are the comments that match the current filters, with a set of buttons and a checkbox located above and below the comments. Each comment shows the commenter's name, url, and email address if they left them, the title of the post the comment was made on, the time and date of the comment, and the text of the comment. The commenter's url serves as direct link to the commenter's site, the email address serves as a link to send them an email, and the post title serves as a link to the comment and the post on which it was made.
To the right of each comment is a drop button, as seen with on the Manage Posts pages. The default action for comments on the drop button is '''Delete''' - to delete the comment. You may select one of the other items from the list, though, if you don't want to take the default action. These other actions are '''Spam''' - to mark the comment as spam, '''Approve''' - to approve the comment, '''Unapprove''' - to unapprove a previously approved comment, and '''Edit''' - to modify a comment.
To the left of the comment is a checkbox. You can tick the boxes to mark one or more comments, allowing you to execute actions against groups of comments. The number of comments marked will be indicated above and below the list of comments. The comments you select will be remembered, allowing you to page through all the comments that match the current filter. The number will remind you that you have comments selected that might not be displayed on the current page. Next to the number of selected comments (which says "None selected" if you've not yet selected any) is another checkbox. If you tick this box, all comments that match the current filter will be selected. If you clear this checkbox, all comments that were previously selected will be unselected.
'''Note''': this '''will''' select comments not displayed on the current page!
Once you have some group of comments selected, you can execute a single action against them. Just click the button at the top or bottom of the page for the action you wish to perform. As with the drop button, your choices are to '''Delete''' the comment, mark the comments as '''Spam''', '''Unapprove''' previously approved comments, and '''Approve''' unapproved comments.
=== Tags ===
To manage your site's tags, select '''Tags''' from the main menu, or type '''Q''', then '''6'''.
<html><img src="manage_tags.jpg" style="height: 200px; width: 300px; padding: 5px; float: right;" /></html>
At the very top of the tags page is a '''search box'''. After your site has been in use for a long time, you can have quite a few tags. Use the search box to narrow the number of tags which you are viewing.
Below the search box you will see the tags which you have used on your site. These will be of various heights, as a visual cue to how frequently they have been used, and each one will have a number beside it denoting the exact number of times it has been used.
Select tags by clicking on them. A second click on a tag de-selects it.
To '''rename''' a tag, click on it, type the new name in the text box below the tags, then click the '''Rename''' button.
To '''merge''' multiple tags into one tag, click on each tag you want to merge, type the name you want to give the tags in the text box below the tags, then click the '''Rename''' button.
To '''delete''' one or more tags, click on each tag you want to delete, then click the '''Delete Selected''' button beneath the tags.
=== Options ===
<html><img src="options.jpg" style="height: 300px; width: 300px; padding: 5px; float: right;" /></html>
Options for your site include such things as the site title, tagline, number of posts per page, time zone, etc.
To edit your site's options, either select '''Options''' from the main menu, or type '''Q''', then '''O'''.
Options are searchable by using the search form at the top right of the page.
=== Themes ===
<html><img src="manage_themes.jpg" style="height: 300px; width: 300px; padding: 5px; float: left;" /></html>
To change your theme select '''Themes''' from the main menu. This will take you to the themes page.
The themes page is divided into two section. The top section contains one item, your current active theme. The bottom section contains all the themes that are available for you to use.
Each theme will show several pieces of information. To the far left is the name of the theme, its developer, and a screenshot of what it looks like in use. In the middle is a short description of the theme. For inactive themes, to the right is a dropbutton containing available actions for the theme. Your active theme will not have a dropbutton.
If a theme is inactive, the only available action is to '''Activate''' it. Clicking on an inactive theme's dropbutton automatically deactivates the theme that is currently active, and activates the theme you selected.
=== Plugins ===
<html><img src="manage_plugins.jpg" style="height: 300px; width: 300px; padding: 5px; float: right;" /></html>
To manage your plugins, select '''Plugins''' from the main menu. You will be taken to your plugins page.
The plugins page is divided into two main sections. At the top of the page is a list of the plugins you have activated. The bottom of the page contains a list of the plugins you have installed, but have not yet activated. Even if you have not added any plugins yourself, this section will contain the plugins that come with Habari.
To the right of each plugin is a dropdown button containing the actions that it is possible for you to perform with the plugin. For inactive plugins, the only possible action is '''Activate''', which you may do by clicking on the dropdown button. Once you do so, the plugin is removed from the list of inactivate plugins and transferred to the list of active plugins.
Active plugins can have several actions associated with them. The most common are '''Configure''', which opens a form in which you can set the configuration options for that plugin, and '''Deactivate''', which removes the plugin from the list of active plugins and returns it to the list of inactive plugins. If there is no configuration possible for a plugin, the only action you will be able to perform with it is to deactivate it.
Each plugin that needs to be configured will have different options, but they all work the same general way. There will be a series of fields to fill in, then a '''Save''' and a '''Close''' button. After setting the plugin options to your satisfaction, click the '''Save''' button to save the changes to the database. Click the '''Close''' button to close the configuration dialog.
=== Users and Groups ===
=== Logs ===
Habari keeps records of most of the interesting things about which it knows. It records each time someone logs in, each time a new item is posted, and the like. By selecting Logs from the main menu (shortcut: Q L), you can review the recent activity on your site.
At the top of the page is the familiar loupe and filter input box, allowing you to control what log messages to display. At the top of each column of data is a drop-down allowing you to filter the logs by specific elements. For example, you can select all the log entries generated by a specific IP address, or all logs generated by a specific user. You can apply multiple filter controls, allowing you to drill down to all authentication messages from a specific IP address and a specific user.
To the left of each log entry is a checkbox, allowing you to select one or more log entries. Selected entries can be manually deleted.
It is important to note that these logs are not permanent, and will expire after some time (usually a few weeks).
== Importing Content ==
Habari is able to import post, comment, category/tag, and user data from a growing [[list of importable platforms]]. To import content, first activate the corresponding Importer plugin on the plugins page (Q P in the Main Menu) and then follow the steps detailed on the Import page.
Once the import process is complete your old data has been copied and can be managed through Habari, and can be deleted from the other platform if desired.
== Extending Habari ==
By design, Habari is built with the basic set of capabilities needed by everyone to create and maintain a blog or simple website. It is, however, also designed to be highly extensible through the use of plugins and themes other than those with which it ships.
=== Themes ===
Your site's theme is it's basic look and feel as seen by your visitors. Habari comes with several themes.
* Charcoal - Charcoal is a unique theme designed to highlight some of Habari's capabilites.
* Mzingi - Mzingi is a basic theme designed to serve as a foundation from which you can create your own themes.
* K2 - K2 is one of the most popular themes in the blogging world.
You aren't limited to these themes in your choices. There are many more themes listed in Habari's theme repository at http://wiki.habariproject.org/en/Available_Themes
=== Plugins ===
Habari comes packaged with several plugins. These are intended to serve as examples of best practices in plugin creation for developers, and to provide useful functionality to everyone.
====Miscellaneous Plugins====
* Core Dash Modules - These included a latest posts module, a latest comments module, and a log module. Activate this plugin to get an overview of the latest activity on your site whenever you log in.
* Pingback - Enable this plugin to send pingbacks to sites to which you link in your posts.
* Spamchecker - Activate this plugin to perform a basic spam check on comments that your site receives.
* Undelete - We all make mistakes, including deleting posts that we later wish we had kept. Activate this plugin to retain deleted posts, so if you change your mind or discover you made a mistake after deleting something, you can recover it.
* ThemeHelper - This plugin is relied on by some themes. It is especially helpful if you create your own themes.
====Media Plugins====
* Habari Media Silo - In Habari, a media silo is a way to manage media that you would wish to embed into your posts. Activate the Habari Media Silo in order to be able to upload images to your site and embed them in your posts with the proper HTML tags with a simple click of the mouse.
* Flickr Silo - Activate the Flickr Silo to access the images you have stored on Flickr with the same ease with which you manage locally stored images.
* Viddler Silo - Activate the Viddler Silo to access the videos you have stored on Viddler.
====Importers====
* Wordpress Importer - Activate the Wordpress Importer to import your content from Wordpress into Habari.
* Serendipity Importer - Activate the Seredipity Importer to import your content from Serendipity into Habari.
In addition to the plugins that are packaged with Habari, dozens of other plugins have been created to extended the software's functionality. These cover everything from WYSIWYG editors, to contact forms, to plugins to allow you to implement custom URLs for your site. Your can find an extensive list on the [http://wiki.habariproject.org/en/Available_Plugins wiki plugin page], and even more at the [http://www.habariproject.org/dist/plugins/ Habari Distribution Directory].
== Getting Help ==
=== Forums and Mailing Lists ===
There exist two mailing lists for Habari, one focused on end-user support, and the other focused on nitty-gritty development and programming issues.
[http://groups.google.com/group/habari-users habari-users] is entirely focused on Habari end-user support. This is the first place to ask for help. When reporting problems, please provide as much information as you can. Be sure to accurately copy any specific error messages you receive. If you can reliably reproduce a problem, describe in detail the steps necessary to do so. It is often helpful to provide a link to your site, so that others can see the problem first-hand, but at the least please indicate who your hosting provider is.
[http://groups.google.com/group/habari-dev habari-dev] is focused on Habari development: programming, plugin design, and the like. All of Habari's long-term development takes place on this list, and ideas and suggested, debated, and revised. If you're interested in helping Habari programming, this is the list for you.
=== Reporting Bugs / Suggesting Improvements ===
Habari succeeds because people like you help improve it every day. One of the easiest ways to contribute to the success of Habari is to report problems. The [http://groups.google.com/group/habari-users habari-users] mailing list is the first place that problems should be reported: if the issue is known, you'll be provided with confirmation of that fact and hopefully some information to help you work around the problem until it is fixed. If you identify a new problem, you may be asked to file a bug report. It's easy to do!
[http://trac.habariproject.org/ Habari Trac] is the place to file bug reports. Before you can file bugs, you need to register an account with the system. Click the <tt>Register</tt> link at the far right of the screen. Once you've created an account you can log in by clicking on the <tt>Login</tt> link. Once logged in, you can file a new ticket!
The first thing you need to provide is a succinct summary of the problem. Be concise, but don't be vague: the summary you provide is the first thing people will read when reviewing bugs to be fixed, so make sure that the developers get a jumpstart on your problem by supplying them with a meaningful summary. If you see an HTTP 500 error page every time you try to log in, a good summary might be
<pre>
HTTP 500 error every time I try to log in
</pre>
This immediately indicates the specific error and the action that produces it. A poor summary for this problem would be
<pre>
Can't log in!
</pre>
This does not indicate the specific error you see.
Summaries are important! Please try hard to provide a meaningful summary.
Next you indicate whether your ticket is due to a defect with Habari, or if you're simply making a suggestion for enhancement. The Habari developers love to see suggestions for enhancements because they help steer the long-term growth of Habari.
Below this you enter a full description of the problem or suggestion. Be as verbose as possible. If you have a specific error message, paste it here. If you have log data, paste it here. If you can reproduce the problem in one or more ways, describe in detail each of the ways to do so. The more information you can provide, the greater the chance of a developer fixing the problem! You can also upload screenshots if that helps to describe the problem: click the checkbox labeled "I have files to attach to this ticket". This will allow you to upload files that will be attached to your report.
In the section labeled <tt>Ticket Properties</tt> you can fine-tune some details about your ticket. If you're not sure what any of these mean, leave them all at the default values.
* Priority is how severe the problem is: most items can be left at "major", though issues involving data loss or security considerations can certainly be escalated to "critical" or "blocker".
* Most of the issues you open will likely be about the Habari software itself, but we also like to be alerted to bugs in our documentation and our website! Suggestions for improvement for the documentation and website are also warmly encouraged.
* Keywords are tags you can assign to your ticket. If you report a problem logging in, you might supply the keyword "login", for example.
* Ignore the CC field for now.
* It's usually best to leave the Milestone drop-down to "unassigned", and allow a Habari developer to set this as they estimate when the problem will be fixed.
* Select the version of Habari that you're using from the Version drop-down.
* Leave the "Assign to" field blank.
When you're all done, click "Submit ticket". This will add your ticket to the system, and a Habari developer will eventually review it. You might want to bookmark your ticket, and come back to check on it periodically: the Habari developers might ask you questions in comments to your ticket, seeking additional information or asking you to try a proposed fix to confirm that it works for you.
Please keep in mind when filing tickets for bug reports or feature enhancements that the list of tickets is completely public. '''DO NOT''' post any passwords or other sensitive information.
==== A note about security issues ====
As mentioned above, all items filed in the Habari Trac are public. If you are reporting a security problem, or a potential security problem, we respectfully ask that you first contact security@habariproject.org so that we can review the situation with you without jeopardizing our users' sites. If you open a ticket with details of how to execute an attack against a Habari installation, someone somewhere '''will''' see that information and exploit it.
[[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 23 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 23 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 23 add a line:
<source lang="php">Format::apply( 'autop', 'post_content_excerpt' );</source>
Then change line 23 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 ==
Welcome to Habari Version 0.5!
This latest release of Habari contains some major improvements and feature changes.
* There is a new administrative interface, lovingly named Monolith
* Several translations are being shipped with Habari. These include the default English, as well as Danish, German, Japanese, and Traditional Chinese
* We now include three themes in the download
* In addition to using MySQL and SQLite, you now have the choice of using PostgreSQL as your backend database
* Session handling has been improved
* Limited UTF8 support has been added
* There are importers for both WordPress and Serendipity
* Improved SQLite security out of the box
* Habari's form creation interface has been revamped, allowing for more customization of forms by plugins and creating an infrastructure for creating new content types.
== Known Bugs ==
Close to 300 bug fixes and improvements have been made since the last release, but many are still listed on [http://trac.habariproject.org/habari/timeline trac]. Many of these involve working with multibyte characters.
==Credits==
These release notes were compiled by the [http://wiki.habariproject.org/en/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.)
[[Category:Manual]]
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.)