mwForum Administration FAQ

This file contains miscellaneous administration notes and explanations.

Contents

Common User Problems

The #1 problem users are having is bookmarking forum_show.pl instead of the correct forum.pl, resulting in posts from the previous session not getting marked as old. Make sure that at least you as the admin understand this problem, so that you don't increase confusion by putting wrong links on your website and are able to help your users when they're complaining about new posts that don't go away etc. A common error of users (and admins) is that even when told to bookmark forum.pl, they first click on that link and then bookmark - but that way, because of the redirection, they'll end up with a bookmark for forum_show.pl. Tell them to drag & drop the forum.pl link to their bookmarks/favorites bar/menu, that's usually the simplest way to add a correct bookmark.

A close second is the general problem of not understanding the dual nature of the new/old and unread/read status, which is somewhat unique to mwForum (although other applications like newsreaders aren't much different). Make sure to have read and understood the icon legend on the forum's help page. It's about as short and precise as I can explain the system.

Attachment Directory

The attachment directory's original purpose is for file attachments to posts. Now it is also used by other features as a general storage directory for various files (incl. avatar images, feed XML files, captcha images, PGP keys, OpenID caches). If you only need the directory for those purposes, you don't have to enable post attachments themselves. It would make sense to rename the directory to a more general name such as "storage" or "dynamic data" directory, but that would also create upgrade work and confusion, as the old term would remain in many posts in the support forum and in option names, etc.

To set up an attachment directory, first create the directory and set its permissions so that the executed Perl scripts can write in it. Example:


shell> mkdir /usr/local/apache/htdocs/mwf/attach
shell> chmod 777 /usr/local/apache/htdocs/mwf/attach

Set the $cfg->{attachFsPath} option in MwfConfig.pm to the filesystem path of the created directory. Set the attachUrlPath option on the forum options page to the corresponding URL path, which will often be the part after "htdocs" in the filesystem path, but may be completely different.

Unfortunately, figuring out what the filesystem path, URL path and permissions should be may be difficult for people with little webmastering knowledge. In documentation by hosting providers and server software, others terms for "filesystem path" as used in mwForum may be "file path", "disk path", "physical path", "absolute path" and the like, whereas alternative terms for "URL path" may be "virtual path", "relative path" and others.

Post Attachments

Security warning: letting untrusted users upload files to a webserver is always a serious risk. Unless you know a good deal about web security, and are also willing to commit some time to a secure webserver configuration, I strongly recommend that you don't enable attachments (or uploaded avatars).

If you want to enable post attachments, create an attachment directory and set the related options as documented above. In addition, enable the attachments option on the forum options page. Attachments also need to be enabled per board on the respective board options page.

Make absolutely sure that files in the attachment directory cannot be executed over CGI, PHP etc! Failing to do so will make your server easy prey for any wannabe hacker. If you're not sure, don't enable attachments in forums with untrusted users.

Put the attach.htaccess file from the example directory into your attachment directory and rename it to ".htaccess" (mind the leading dot). Unfortunately, not all webservers understand all of the included options (a default Apache installation does). Also, webserver admins can restrict which options can be used in .htaccess files. If any of the unimportant of these options are causing errors, remove them, but keep the rest. If the important ones or the whole file don't work, don't enable attachments in forums with untrusted users.

Note that since attachments are stored in a filesystem directory and not in the database, attachments to posts in private boards are not strictly private themselves, everybody can theoretically download them. If your webserver is configured to not allow browsing the attachment directory, and the filenames are not easy to guess, the files should be reasonably safe, though. Also see the related option in the .htaccess file.

For security and compatibility reasons, only 7-bit ASCII alphanumeric characters (plus . and _) are accepted for filenames, all others are removed. Be very careful if you want to loosen those restrictions.

Try to make use of the possibility to serve the attachments from a different domain or at least subdomain by setting the attachUrlPath forum option accordingly. A different subdomain will of course only prevent cookie theft if the cookie path doesn't allow access from every subdomain.

mwForum will not try to filter out malicious content, as that is a hopeless task that would just give a false sense of security.

PostgreSQL

"PostgreSQL is a powerful, open source relational database system. It has more than 15 years of active development and a proven architecture that has earned it a strong reputation for reliability, data integrity, and correctness."

The PostgreSQL support is meant to be used by admins with PostgreSQL experience, as there will be less hand-holding.

Installation

  1. Ignore the MySQL-specific instructions in Install.html and create the database, user account and permissions yourself. The database should use "ENCODING='UTF8'", otherwise users will get error messages when they try to insert text containing characters outside the chosen encoding's coverage.
  2. Install the DBD::Pg Perl module.
  3. Set $cfg->{dbDriver} in MwfConfig.pm to "Pg".
  4. If you want to use Unix sockets instead of TCP/IP connections, set $cfg->{dbServer} to the socket directory, e.g. "/tmp".
  5. If you want to put the tables into a schema other than "public" (mainly in the case of a multi-forum installation), set $cfg->{dbSchema} to the name of that schema.
  6. Continue with the "perl install.pl" step from the normal instructions in Install.html.

Notes

You might want to enable auto-vacuuming in PostgreSQL 8.1 and newer or perform frequent cronjob-controlled vacuuming in 8.0, since the database may otherwise grow very fast. Due to the way the multi-versioning works in PostgreSQL, it will copy rows every time they're changed, and not automatically reuse old copies. And rows in some tables like the user table are updated on every page hit. mwForum's cron_jobs.pl performs VACUUM ANALYZE for all tables, and should usually be run nightly anyway, but may not be enough for busy forums.

Equality string comparisons in PostgreSQL are normally case-sensitive, which means that for example usernames have to be spelled with the correct case during login, user search etc. PostgreSQL 8.4 includes a custom datatype "citext" in the contrib directory. If this type is installed and the type of mwForum's VARCHAR and TEXT columns changed to it, the behaviour should match the more convenient case-insensitive behaviour of MySQL. Similar extensions probably exist for older PostgreSQL versions somewhere. If $cfg->{dbCitext} is set to 1 or if the install.pl and upgrade.pl scripts are passed an "-i" option, they will use citext instead of VARCHAR and TEXT columns. Existing databases can be converted with the help of util_citext.pl.

For forum search and some substring search features of the user admin page (i.e. everything using ILIKE), it depends on the database cluster's locale settings how case comparison is handled. Case-insensitive search in UTF-8-encoded databases only works starting with PostgreSQL 8.2. In PostgreSQL 8.4 and newer, the locale settings can finally be set on the database level instead of the cluster level.

The connection overhead of PostgreSQL is significant compared to MySQL. Using mod_perl and persistent database connections (Apache::DBI) is recommended.

Some of the aggregated views on the user admin page require a GROUP_CONCAT() function. The file util_groupconcat4pg.sql contains custom functions that you can install into the database for this purpose.

SQLite

"SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine. SQLite is the most widely deployed SQL database engine in the world."

SQLite isn't meant to be a database system for many concurrent users. I don't know how well it scales in practice, but it might only be suitable for smaller forums.

Installation

  1. Ignore the MySQL-specific instructions in Install.html.
  2. Install the DBD::SQLite Perl module. It comes bundled with the SQLite library itself.
  3. Set $cfg->{dbName} in MwfConfig.pm to a full file path instead of just the database name, e.g. "/var/db/mwforum.db". The directory in which this will be located must be writable by the webserver user account (and therefore shouldn't be the same as the script directory). The database file will be automatically created later.
  4. Set $cfg->{dbDriver} to "SQLite". Other db options don't matter.
  5. Continue with the "perl install.pl" step from the normal instructions in Install.html.

Notes

String comparisons are case-sensitive in SQLite, which means that for example usernames have to be spelled with the correct case wherever usernames need to be typed in.

Substring searching with the LIKE operator, used for e.g. the forum search, is also case-sensitive for non-ASCII characters, which can be a real problem for non-English languages. If you enable the sqliteLike forum option, the forum provides a custom function to SQLite that makes forum search and user admin page search case-insensitive, but has a serious performance impact.

Since at the time of this writing, no SQLite release supports dropping columns, obsolete columns may remain in the database as dead weight after an upgrade. The util_migratedb.pl script can be used to get rid of these, but I'll leave the details as an exercise to advanced admins.

With older versions of SQLite and DBD::SQLite I got "closing dbh with active statement handles" error messages at times. These seem to be bogus and can be ignored.

Some of the non-essential views on the user admin page require a newer SQLite version that includes GROUP_CONCAT().

If using SQLite 3.7.0 or later, you should probably enable Write-Ahead Logging for the database, by opening the database file in the sqlite3 tool once and using the pragma command:


shell> sqlite3 mwforum.db


sqlite> PRAGMA journal_mode = WAL;
sqlite> .q

New/Old Unread/Read

This text explains some of the technical details of the new/old and unread/read computation.

"New" computation (yellow icons): Posts are treated as new if they have been added since the user's last visit to the forum.

A single prevOnTime value is saved in the user's database record. All posts with a newer post time are counted as new, all others are counted as old. The prevOnTime value is updated each time the user enters the forum through forum.pl (it's set to the lastOnTime value, which in turn is set to the current time each time the user hits a page on the forum).

"Read" computation (checkmarked icons): A topic and all posts within are rated as read after the topic is displayed by the user - whether the user actually read any of the posts is beyond the forum's knowledge, of course.

For each user and topic that he reads, a lastReadTime value is stored in a separate table. Posts (and topics with the latest post newer) than that value are rated as unread, all others are rated as read. Each time a topic is displayed, the lastReadTime value for that topic is updated.

Unfortunately, the fact that topics can be spread over multiple pages makes things a little more complicated than described above. Since we don't want to see all posts of a topic as read when switching to another page inside the same topic, backups of the last visited topic id and related lastReadTime value are stored in the user's record, and used instead of the data from the extra table when the user switches to another page in the same topic. There are also a few other optimizations that make unread post tracking the most complicated part of the code.

For performance reasons, the read status is only stored in the way described above, not per user and individual post, which would generate a huge amount of data and slow down database queries. This has some consequences, e.g. new posts by a user cannot be marked as read if there were other posts in that topic posted in parallel, and once you "leave" a topic or open another topic in another tab, posts on other topic pages are rated as read even though they've never been on screen. Also, to keep the table size down, posts which are older than a certain threshold value (30 days by default) are treated as read, too.

Time Formatting

An excerpt from a POSIX strftime manpage, showing possible formatting parameters. Your platform's library might differ, especially with the more exotic parameters.


%a - The abbreviated weekday name according to the current locale.
%A - The full weekday name according to the current locale.
%b - The abbreviated month name according to the current locale.
%B - The full month name according to the current locale.
%c - The preferred date and time representation for the current locale.
%C - The century number (year/100) as a 2-digit integer.
%d - The day of the month as a decimal number (range 01 to 31).
%D - Equivalent to %m/%d/%y. (Some people confuse this with %d/%m/%y)
%e - Like %d, the day of the month as a decimal number, but a leading zero
     is replaced by a space.
%G - The ISO 8601 year with century as a decimal number.
%g - Like %G, but without century, i.e., with a 2-digit year (00-99).
%h - Equivalent to %b.
%H - The hour as a decimal number using a 24-hour clock (range 00 to 23).
%I - The hour as a decimal number using a 12-hour clock (range 01 to 12).
%j - The day of the year as a decimal number (range 001 to 366).
%k - The hour (24-hour clock) as a decimal number (range 0 to 23); single
     digits are preceded by a blank. (See also %H.)
%l - The hour (12-hour clock) as a decimal number (range 1 to 12); single
     digits are preceded by a blank. (See also %I.)
%m - The month as a decimal number (range 01 to 12).
%M - The minute as a decimal number (range 00 to 59).
%O - Modifier: use alternative format, see below.
%p - Either 'AM' or 'PM' according to the given time value, or the
     corresponding strings for the current locale.  Noon is treated as
     'pm' and midnight as 'am'.
%P - Like %p but in lowercase: 'am' or 'pm' or a corresponding string for
     the current locale.
%r - The time in a.m. or p.m. notation. In the POSIX locale this is
     equivalent to '%I:%M:%S %p'.
%R - The time in 24-hour notation (%H:%M). For a version including the
     seconds, see %T below.
%S - The second as a decimal number (range 00 to 61).
%T - The time in 24-hour notation (%H:%M:%S).
%u - The day of the week as a decimal, range 1 to 7, Monday being 1.
     See also %w.
%U - The week number of the current year as a decimal number, range 00 to
     53, starting with the first Sunday as the first day of week 01.
     See also %V and %W.
%V - The ISO 8601:1988 week number of the current year as a decimal
     number, range 01 to 53, where week 1 is the first week that has at
     least 4 days in the current year, and with Monday as the first
     day of the week. See also %U and %W.
%w - The day of the week as a decimal, range 0 to 6, Sunday being 0.
     See also %u.
%W - The week number of the current year as a decimal number, range 00 to
     53, starting with the first Monday as the first day of week 01.
%x - The preferred date representation for the current locale without the
     time.
%X - The preferred time representation for the current locale without the
     date.
%y - The year as a decimal number without a century (range 00 to 99).
%Y - The year as a decimal number including the century.
%+ - The date and time in date(1) format.

Users have to manually adjust their timezone to Daylight Saving Time (aka Summer Time), since there's way too many different and complex DST rules to implement, and many of them often change.

Logging

Logging is done to the "log" table in the database, and needs to be enabled with the logLevel forum option. Unless actually needed, it should be left disabled. Different logging methods (e.g. to a plaintext file) could be implemented via an Event plugin.

The forum provides a log view page with some filtering and sorting options. More advanced statistical analysis and manipulation etc. has to be done manually with a database client application on the SQL level.

If you need to know the exact details about which action/event uses which log level, action names and other parameters, you'll have to look at the code (search for logAction). Generally, the actions that actually modify the database are log level 1. Also logged with this level are requests denied due to user bans and IP/hostname blocks. Some display scripts like those that display actual content like the forum, board and topic pages are level 2. Actions that just display forms or less important info pages are level 3.

Example: Basic log dump


sql> SELECT * FROM log ORDER BY logTime;

Example output (various columns hidden to make table fit on page):


+-------+--------+---------+--------+---------+---------+--------+
| level | entity | action  | userId | boardId | topicId | postId |
+-------+--------+---------+--------+---------+---------+--------+
|     1 | user   | delete  |      1 |       0 |       0 |      0 |
|     1 | forum  | search  |      1 |       0 |       0 |      0 |
|     1 | user   | delete  |      1 |       0 |       0 |      0 |
|     1 | board  | options |      1 |       5 |       0 |      0 |
|     1 | post   | edit    |      1 |       8 |     538 |   4390 |
+-------+--------+---------+--------+---------+---------+--------+

Example: Who killed post #4451?


sql> SELECT userId
sql> FROM log
sql> WHERE entity = 'post'
sql>   AND action = 'delete'
sql>   AND postId = 4451;

Example: Full log with resolved user/board/topic names and the first 20 characters of involved posts


sql> SELECT log.logTime, log.entity, log.action, users.userName,
sql>   boards.title, topics.subject, LEFT(posts.body, 20)
sql> FROM log
sql>   LEFT JOIN users ON log.userId = users.id
sql>   LEFT JOIN boards ON log.boardId = boards.id
sql>   LEFT JOIN topics ON log.topicId = topics.id
sql>   LEFT JOIN posts ON log.postId = posts.id
sql> ORDER BY log.logTime;

Performance Tuning

Things to consider when running big and/or high traffic forums:

When I say high traffic, I'm talking about more than one request per second. Big forums have at least 100,000 posts, really big ones a lot more.

If your forum is hosted on a shared server, especially with cheap mass hosters, there usually isn't very much you can do about performance, since your forum is only one of many processes on an often overloaded server. Some providers host as many as tens of thousands of accounts on a single server.

For high traffic forums, using mod_perl (which embeds a Perl interpreter in the Apache webserver and keeps the Perl scripts compiled in memory) makes a big difference. Many Linux distributions come with easily installable Apache/mod_perl packages, which are already an improvement, but the way they're used is not quite the most efficient. Experienced webmasters might want to use a frontend/backend approach (aka. "reverse proxy") for maximum efficiency, as it's for example described in the mod_perl Guide. Without this approach, mod_perl will waste a good deal of RAM. This is not a mod_perl-specific issue by the way, but also true for PHP and other Apache-embedded interpreters.

Especially with the RAM-hungry mod_perl, it goes without saying that you have to make sure that your server has enough RAM. If the system starts swapping, or if the database doesn't fit into RAM anymore, performance will go down the drain.

If you have really high concurrency, you might want to try using PostgreSQL or the InnoDB engine of MySQL. They use multiversioning, and thus don't lock tables the way the MyISAM engine does. InnoDB doesn't support fulltext indexes however, and normal tablescan searching is also quite a bit slower than with MyISAM. Having mod_perl processes wait several seconds for a query can be a problem. See the PostgreSQL and InnoDB sections in this file for more info.

Big databases need regular maintenance (defragmentation, optimization, and sometimes even repairs). mwForum's cronjob uses the OPTIMIZE command on all tables (except the log), and it's recommended that you run it daily.

Certain modifications that go beyond the intended purpose of features, such as hundreds or thousands of dynamically created boards (yes, more than one person did that) will definitely kill performance if the database format/indexes and other parts of the code aren't properly adapted.

The performance of the forum/main page is dependent on the overall number of posts. So if you keep the expiration time of topics low, even high traffic forums can be fast. Big archives of old topics are only useful if your users actually search them, which only experienced users tend to do anyway.

There are various other performance-relevant options in the configuration. They're usually marked as such.

If you have a dedicated server, even an ancient CPU with enough RAM, Linux/*BSD and a properly tuned Apache/mod_perl setup is easily up to the task of running a fairly big forum. But even a small forum can get you kicked from an ISP whose CGI/MySQL services weren't meant to be used for anything real.

mwForum's indexed search mode uses MySQL's Boolean Full-Text Search or PostgreSQL's Full Text Search feature to speed up searching compared to the normal search mode. More details can be found in the linked MySQL/PgSQL documentation.

MySQL

Up to MySQL 5.5, Full-Text Search can only be used with the MyISAM and Aria storage engines. Starting with MySQL 5.6 and MariaDB 10, InnoDB is also supported.

Activate the indexed search mode with the advSearch forum option. The SQL commands for creating and removing full-text indexes for post bodies are:


mysql> CREATE FULLTEXT INDEX posts_body ON posts (body);
mysql> DROP INDEX posts_body ON posts;

More so than other indexes, the existence of a fulltext index slows down manipulative queries. You might have to remove the index before doing major changes like deleting a board, to prevent these operations from taking too long and getting interrupted by timeouts.

There are two operators that people can use, the usual quotes to group a phrase, and the - prefix operator to skip any results that include the prefixed word or phrase.

MySQL by default doesn't index or find words with less than four characters. This can and should be changed to three characters with the ft_min_word_len option in the MySQL configuration (fulltext indexes need to be recreated after changing that number).

If you replace or amend MySQL's stopword list (which is also included in mwForum to show feedback about non-searchable words), you can supply mwForum with the same list by putting it in MwfConfig.pm, e.g. "$cfg->{stopWords} = [ "hi", "blah", "lulz" ];".

There are various limitations, some of which lie in the nature of full-text indexes, some in MySQL's implementation. It only works for word-like strings, not any random strings with special characters etc. It's therefore not ideal for forums where people often search for technical expressions, like programming or Web design forums. It will only find words that start with the typed characters, not words that have those characters in the middle or end.

For those familiar with mwForum's previous advanced search mode or MySQL's boolean search operators: in mwForum 2.7.3, indexed search mode replaced the advanced search mode, since the latter required explicit use of + and * operators to get useful results, which was too complicated for most users, and inconvenient even for experienced users. mwForum now automatically prefixes all words and quoted phrases with + operators, and postfixes all words with * operators. It removes all special characters outside of quoted phrases, except for the - prefix operator, which is passed on to MySQL. Users will see the stripped search string in the input field, which should hopefully tell them something about what kind of strings MySQL can't find anyway. Advanced users can't manually use any additional operators like <>~() anymore, but those are rarely useful anyway.

PostgreSQL

PostgreSQL's Full Text Search is more advanced than MySQL's. The main advantage worth mentioning here is the built-in support for word stemming in various languages. Some reading of the PostgreSQL documentation linked above is recommended. At a minimum, read about the available "configurations", and choose one ("english" is used as the default).

Activate the indexed search mode with the advSearch forum option. The SQL commands for creating and removing full-text indexes for post bodies are:


psql> CREATE INDEX posts_body ON posts USING gin(to_tsvector('english', body));
psql> DROP INDEX posts_body;

Put the name of the chosen configuration into $cfg->{pgFtsConfig} in MwfConfig.pm if it's different from the default "english". Make sure the configuration name is the same as during index creation, or the index won't be used by PostgreSQL.

The typed search expression is parsed with plainto_tsquery(), which doesn't support any operators. If the whole expression is placed in quotes though, mwForum will make an additional filter pass which removes any results that don't contain the exact (but case-independent) expression.

Notes for both DBMS

The search expression used internally (the input stripped of special characters and stopwords, with words stemmed in the case of PostgreSQL) is shown in an info box below the search form, unless it is identical to the input. Words from both the input and this expression are highlighted in the found posts. mwForum doesn't highlight exactly the same words that the DBMS have actually found, since they don't give us that information.

Indexed search is only used for normal post bodies. Raw post bodies and topic subjects are searched with the standard LIKE-based method. In the case of raw post bodies, this makes it possible to find "technical" content that couldn't be found easily or at all with full-text search geared towards human language.

While searching for less common words is accelerated, searching for common words may be slower.

The feature can actually be used with or without actually creating a full-text index, but using it without is slow and mostly pointless.

Upgrade and other scripts that do a lot of searching and replacing may take a lot longer with a full-text index present.

Local Strings

If you want to change one or more user interface strings without modifying the original language modules, you can create modules that override certain strings. These modules must be named by appending "Local" to the language name, as in "MwfEnglishLocal.pm". Inside, they should look like:


$lng->{hdrWelcome} = "Hello,";
$lng->{errUser}    = "Oops!";
1;

Instead of completely overriding the original string, you could also add more text, e.g. to add new questions to the FAQ. This can be done by using the .= operator instead of the = operator.

Local Options

If you want to add your own options or those of plugins to the forum options page, you can create a file called MwfDefaultsLocal.pm, and add options there. The module needs to push new options onto the options array as shown in the following example. For more examples of valid fields, look at MwfDefaults.pm.


push @$options, {
  section  => "Secret Options",
  id       => "s3cr3t",
};

push @$options, {
  name     => 'firefoxAd',
  type     => 'checkbox',
  title    => "Show Firefox button to MSIE users?",
  help     => "You need to supply your own firefox.gif.",
  default  => 0,
};

Hidden Options

Some options are too unimportant, obscure or experimental to be added to the already long list on the forum options page. Some of these options are listed here. Due to its nature, this list will be incomplete and may contain obsolete entries. Any of these options can be added to MwfConfig.pm. Options documented in context elsewhere won't be repeated here.

replHtmlEnt
Set to 1 to replace all HTML entities (aka "character entity references" and "numeric character references") in input with proper Unicode characters. Otherwise, they get escaped like all HTML, and show up verbatim in posts. Requires HTML::Entities Perl module from the HTML::Parser distribution.
nph
Set to 1 to enable "no-parsed-headers" mode, for non-standard environments that require the CGI script to print the whole HTTP response verbatim. Experimental and not maintained.
maxOnlUserNum
Max. number of online users listed on forum page.
maxOnlUserAge
Max. time in seconds that users are considered online and listed as such on forum page.
maxNewUserNum
Max. number of new users listed on forum page.
maxNewUserAge
Max. time in days that users are considered new and listed as such on forum page.
maxUserListNum
Max. number of members/moderators/subscribers listed on board/group info pages.
usersPP
Number of users per page on user list and user admin pages.
attachPP
Number of attachments per page on attachments list page.
attachGallPP
Number of images per page on attachments list page in gallery mode.
attachNameLen
Max. allowed attachment input filename length and extension length. Longer filename components will be truncated. Names may get a little longer afterwards through numbering for uniqueness etc.
attachImgThbW, attachImgThbH, attachImgThbS
Max. allowed width, height and file size for embedded attached images before they get thumbnailed if the attachImgThb forum option is enabled. Defaults are 150 pixels respectively and 15360 bytes.
attachImgThbQ
JPEG quality of thumbnails for attached images. 1 = min. quality, 100 = max. quality. Default is 90.
attachImgRszW, attachImgRszH, attachImgRszS
Max. allowed width, height and file size for attached images before they get resized if the attachImgRsz option is enabled. Defaults are 1280 pixels, 1024 pixels and 204800 bytes.
attachImgRszQ
JPEG quality of thumbnails for resized images. 1 = min. quality, 100 = max. quality. Default is 80.
attachImgDef
Set to 1 to embed attached images by default.
noGd, noImager, noGMagick, noIMagick
Set either to 1 to disable the use of the GD, Imager, Graphics::Magick or Image::Magick Perl modules, e.g. for debugging purposes or if an installed module version is buggy. Normally, mwForum will try to use the listed modules in that order.
userFlagSkip
Set to two-letter country code of a country for which no flags should be shown on topic pages. Can be used if the majority of users comes from a single country and only the rest should be highlighted.
geoIpCacheMode
Caching mode of Geo::IP module, GEOIP_STANDARD = 0, GEOIP_MEMORY_CACHE = 1, GEOIP_INDEX_CACHE = 4, GEOIP_MMAP_CACHE = 8. You have to specify the number, as the constants are unavailable. Some modes may fail without a useful error message. Default is GEOIP_MEMORY_CACHE.
uaGraphType
Method of showing pie charts on the browser (user agent) statistics page. "none" = disable pie charts. "GD::Graph" = use that Perl module to generate images. "Imager::Graph" = use that module to generate images. Both these methods include their output inline as data URLs and require a readable font configured with the ttf option below. "GoogleVis" (the default) = use the Google Chart API (Terms of Use) to generate SVG pie charts with Javascript. This method doesn't require any Perl modules, but includes Javascript from Google, which may not be acceptable for security reasons.
ttf
Set to the filesystem path of a TrueType font file for any features that might require one and use this option (currently only the GD::Graph and Imager::Graph flavors of the chart feature above).
postEditStTime
Number of seconds that users can edit a post after posting without an edit timestamp showing up. Default is 120 seconds.
queryLog
Location of a file where all performed SQL queries are logged, e.g. "/var/log/forum_query.log". For performance reasons, only leave this enabled while debugging.
runLog
Location of a file where all invocations of IPC::Run are logged, e.g. "/var/log/forum_run.log". Currently IPC::Run is only used for calling GnuPG.
fScriptUrlPath
Set to 1 to force use of scriptUrlPath for all constructed URLs, not just those in shell/cron/spawned scripts. Might be useful for certain path masking situations.
noArchive, noSnippet
Set to 1 to add these to the robots meta tag to keep some search engines from archiving full pages resp. snippets. Also see noIndex forum option.
relRedir
Use relative Location header in redirections. Violates HTTP spec, but might help in some specific situations.
dbSync
Value for SQLite's "synchronous" pragma (default is "OFF"), or value of PostgreSQL's "synchronous_commit" parameter (default determined by cluster configuration).
dbTableOpt
MySQL or PostgreSQL table options used when creating tables in install.pl and upgrade.pl, e.g. "CHARSET=utf8mb4 ENGINE=aria PAGE_CHECKSUM=1 TRANSACTIONAL=1". Default is "CHARSET=utf8" for MySQL.
dbHideError
Set to 1 to hide database error message details from normal users.
noLogUserId
Set to the numeric ID of a user whose requests you don't want to log in the forum log.
noGuestCookies
Set to 1 to disable the feature that lets not-logged-in people use the Mark Old button the set a prevOn cookie for new/old tracking. This is for legal reasons, in case you never want to give cookies to people who haven't agreed to a forum/privacy policy. Doesn't affect guests who already have the cookie.

User Titles

User titles are strings (plaintext, images or HTML) added after the username on the topic and user info pages (most other places are space-limited). A typical example would be "JohnDoe (Admin)" instead of the plain "JohnDoe".

Only forum admins can set these titles on the user admin options pages (user_admopt.pl), so they can be used for example to authenticate employees in a corporate forum. Since the title isn't part of the clickable username link, no user can choose his name to look like a real user title.

On the user admin options page, an individual title can be typed in, or a predefined title can be selected from the autocompletion list (requires browser HTML5 support). The predefined titles can be configured with the userTitles forum option.

Plaintext titles are put into parentheses automatically when displayed, unless the string already contains parentheses. Certain characters that are special in HTML (apostrophes, ampersands, angled brackets) must be manually HTML-escaped. Example:


Owner
Administrator
Moderator

If the title starts with an image filename, the forum automatically transforms it into an HTML image tag. In this case the image file must reside in the forum's data directory. If there's text after the filename, it will be used for the title and alt attributes. Example:


owner.png  Owner
admin.png  Admin
mod.png    Mod

A user title string can also be raw HTML. Use this for images or other stuff if you need full control over the HTML attributes, want an image to be a link, etc. Example:


<img class="utl" src="owner.png" alt="Owner" title="Owner">
<img class="utl" src="admin.png" alt="Admin" title="Administrator">
<img class="utl" src="mod.png"   alt="Mod"   title="Moderator">

User Ranks

User ranks are strings (plaintext, images or HTML) shown after the username on topic pages and user info pages (most other places are space-limited and/or can do without the additional performance hit). The rank depends on the number of posts the user has made.

The list of existing ranks is defined with the userRanks forum option. There are no default ranks defined or images bundled with mwForum. The format of user rank definitions is similar to that of user titles (see above), except that every line starts with a minimum post count required to attain the rank. The order of ranks must always be descending. If a user has both a title and a rank, only the title will be displayed on topic pages for space reasons. Simple example for plain textual titles:


5000 Postmaster
3000 Postal Inspector
1000 Senior Postman
100  Junior Postman
0    Nobody

Example using images and text for the title/alt HTML attributes:


3000 5stars.png *****
1000 4stars.png ****
250  3stars.png ***
50   2stars.png **
10   1stars.png *

User Badges

User badges have some functionality overlap with user titles, but have more use cases, and more than one badge can be assigned per user. Badges are displayed as small icons after the username on topic pages (with a short text as icon tooltip), and as big icons with description text on user info pages. Example use cases:

The list of available badges is defined with the badges forum option. There are no default badges defined or images bundled with mwForum. Example with badges for multiple uses:


mod    group group_mod.png   group_mod_big.png   "Moderator"   "User is a global moderator."
gold   admin star_gold.png   star_gold_big.png   "Gold Star"   "User has been awarded the Gold Star Badge."
silver admin star_silver.png star_silver_big.png "Silver Star" "User has been awarded the Silver Star Badge."
bronze admin star_bronze.png star_bronze_big.png "Bronze Star" "User has been awarded the Bronze Star Badge."
basket user  -               sport_basket.png    "Basketball"  "User plays basketball."
tennis user  -               sport_tennis.png    "Tennis"      "User plays tennis."
golf   user  -               sport_golf.png      "Golf"        "User plays golf."
foo    ext   owner_foo.png   owner_foo_big.png   "Foo Owner"   "User is a registered owner of Foo."
bar    ext   owner_bar.png   owner_bar_big.png   "Bar Owner"   "User is a registered owner of Bar."

The first column is the badge's identifier. As usual, identifiers should only consist of the characters a-z_0-9.

The second column is the type: admin badges can only be awarded by administrators, user badges can also be selected by users for themselves, group badges are automatically assigned according to users' group memberships, and ext means badges of this type are assigned by addon code only, which will usually be syncing the badges with some kind of external database.

The third and fourth columns are the small and big icon filenames. If the small icon is "-", the badge doesn't appear on topic pages, only on user info pages. The big icon can be the same as the small icon, and multiple badges can also share the same icons.

The sixth and seventh columns are the title and description texts. Both can contain spaces and therefore need to be limited with quotes. Both are HTML-enabled, which allows you to e.g. insert paragraphs into description texts, but also requires you to to manually HTML-escape stuff that needs escaping, like e.g. apostrophes in the title.

Admins (and users, if allowed to) can select badges from the user_badges.pl page, linked from the user profile page. Admins can set the badge awarded by group membership on the group option pages.

Notes

Topic Tags

Topic tags are strings (plaintext, images or HTML) shown after the topic subject on the board pages. They can be used to classify, categorize or rank topics, depending on what tags the admin defines. Tagging should normally be limited to admins and moderators, as normal users will seldomly use tags in a consistent and useful way, but the feature can also be enabled for all registered users who started the respective topic. Tags are set via the "Tag" link button on the topic page of existing topics.

Topic tags are enabled with the allowTopicTags forum option. The list of existing tags is defined with the topicTags forum option. There are no default tags defined or images bundled with mwForum. The format of topic tag definitions is the same as for user titles (see above), except that every line starts with a short identifier followed by a "=" sign, and that plaintext tags aren't put into parentheses automatically. Example for images with title/alt text:


howto    = howto.png    HowTo
readme   = readme.png   Readme
question = question.png Question

Text Snippets

Text snippets are simple pieces of text that can be inserted into posts and private messages by selecting them from a list. They are also called "templates", "boilerplate text" or "text building blocks" in other applications. The typical use case is any situation where posts should follow a standardized format, and where the snippet contains the outline for that format, e.g. the various bits of information required for a useful bug report.

Available text snippets are defined with the textSnippets forum option as follows: the snippet title comes on its own line surrounded by "[[" and "]]", with no whitespace before or after. The snippet can optionally be limited to certain boards by putting a comma-separated list of numeric board IDs between a "=" and the "]]", as shown in the second example. After that follow the lines of text that are to be inserted into posts. Empty lines are allowed and part of the text.


[[Bug Report]]
Synopsis:
Application Version:
Operating System:
Severity:
Bug Description:

[[Feature Request=7,8]]
Synopsis:
Priority:
Feature Description:

The snippet list is shown after the tag insertion buttons above post/message body fields. It requires the display of those buttons to be enabled with the tagButtons forum option, and will only be shown if there are snippets defined for the current board. Snippets can be limited to private messages by using the fake board ID 0.

Multi-Forums

Using the multi-forum feature, multiple forums can be run from a single set of Perl source code files. This feature is meant to be used with mod_perl installations, where multiple copies of the scripts would take up a lot of RAM and database connections, and where multiple copies of Perl modules with the same name can't co-exist. Each forum still uses its own database, i.e. there's no shared user table or anything like that.

Different forums need to use either different hostnames or different URL paths, because those are used to distinguish between the forums. No additional forum ID parameter is used in URL query strings (that would have been the cleaner solution, but would have required many more changes and would also have reduced database performance). If you want to identify the forums by different URL paths, you have to use your webserver's aliasing features to map the different URL paths to the same physical directory. Example (mod_perl 2.x):


Alias /forum1 /usr/local/apache2/forum
Alias /forum2 /usr/local/apache2/forum
<Directory /usr/local/apache2/forum>
  Options +ExecCGI
  SetHandler perl-script
  PerlResponseHandler ModPerl::RegistryBB
  PerlOptions -GlobalRequest -SetupEnv -ParseHeaders
</Directory>

Installation

  1. Edit the MwfConfigGlobal.pm file. The $gcfg->{forums} option contains the mapping from hostnames or URL paths to different forum configuration files. The example should be mostly self-explanatory. URL paths must not end with a slash.
  2. Create the configuration files by copying the original MwfConfig.pm. Change the package declaration at the top of each file to match the filename without extension (easy to forget).
  3. Optionally, enable sharing of persistent database connections (with Apache::DBI):

    MySQL: set $gcfg->{dbName} in MwfConfigGlobal.pm to one of the used database names. All forums need to use the same database user account.

    PostgreSQL: Create a schema for every forum in the same database. Set $cfg->{dbSchema} in the config files to the respective schema name. All forums need to use the same database user account.

Shell Scripts

Shell scripts get the forum's identifying hostname or URL path via the -f parameter (previously, it used to be passed as the first argument, but that was confusing for the majority of admins, who don't use multi-forums). Examples:

Crontab, using hostnames:


10 3 * * *   user   cd /usr/local/apache/forum && perl cron_jobs.pl -f forum1.example.com 
20 3 * * *   user   cd /usr/local/apache/forum && perl cron_jobs.pl -f forum2.example.com

Calling utility scripts, using URL paths:


shell> perl util_replace.pl -x -f /forum1 replacethis withthis
shell> perl util_replace.pl -x -f /forum2 replacethis withthis

Polls

Like in most other forum applications, polls are added at the top of topics. Polls can be globally disabled and their creation can be limited to admins and moderators. Only the topic's creator or an admin may add a poll to the topic. They can be deleted by the topic creator as long as there haven't been any votes, only admins and moderators can delete them afterwards.

There are single-vote and multi-vote polls. In single-vote polls, each user can only vote once per poll. In multi-vote polls, multiple votes can be cast per poll, one per option, at different points in time.

Polls can be closed by the topic creator and by admins/moderators. This irreversibly consolidates the sum of the votes from the pollVotes table into the pollOptions.votes field, which makes displaying the poll faster and removes the individually trackable votes (that are necessary to prevent cheating in multi-vote polls).

Avatars

Avatar images are supported as uploads and/or as a selection from a local gallery. Users can choose their avatar by following the Avatar link on their user profile page. Since avatars can waste a good deal of bandwidth, users can switch them off in their user options. They're also off by default.

mwForum only allows local avatars, as remote images couldn't be limited to a sane size and they would also allow their respective owners to monitor topic access. Last not least they'd cause issues with SSL.

Avatar Upload

Uploaded avatar images have a configurable maximum file size and width/height. Unless auto-reformatting is enabled (see below), the avatar images must match these width/height values exactly, they're not maximum values like in many other forum applications. This is required because differently sized avatars would cause a more ragged layout the way they're embedded in the text. There isn't enough space in threaded topics for user info/avatars cells on the left side of the posts as in most non-threaded forum applications.

Animated uploaded avatars are not allowed, since animated avatars are every bit as annoying and distracting as animated ads. The Image::Info Perl module is used to check these parameters, and is required if avatar uploading is enabled.

If you want to enable avatar uploading, set the related forum options in the Avatar section, install the Image::Info Perl module and make sure the attachment directory is set up.

Security warning: letting untrusted users upload files to a webserver is always a serious risk. Unless you know a good deal about web security, and are also willing to commit some time to a secure webserver configuration, I strongly recommend that you don't enable uploadable avatars.

If you want to enable the auto-reformatting feature for avatars that don't conform to the specified restrictions (file size, dimensions, no animation), you either need the GD, Imager, Graphics::Magick or Image::Magick Perl modules installed, and they (or rather the underlying libraries) must include support for the web image formats JPG, PNG and GIF.

Avatar Gallery

If you want to enable gallery avatars, create a directory named "avatars" inside the attachment directory (unless that already exists) and a directory named "gallery" inside the "avatars" directory. Upload some fitting JPG, PNG and GIF images into that directory.

The images won't be checked for type and size, but the displayed avatars will be zoomed to the same configured dimensions as the uploaded avatars, so you'd better stick to those dimensions, since images zoomed by the browser don't look all that great. There is also a script named util_resizeavatars.pl that scales all images in the gallery directory to the configured dimensions. It also requires the GD, Imager, Graphics::Magick or Image::Magick Perl modules.

SMTP

Some webspace providers may require SMTP authentication if a web application wants to send email. Also, some people might want to host forums on their home connections, but use the SMTP server of an email provider where they have an account, since most recipient's email providers don't accept emails sent directly from dynamic/home IP addresses.

mwForum supports SMTP authentication and SSL/TLS connection encryption when sending emails with the Net::SMTP or Mail::Sender Perl modules. To enable authentication, Net::SMTP requires Authen::SASL. If the default SMTP authentication mechanism of Mail::Sender doesn't work, you can specify a different one with the $cfg->{esmtpAuthMech} option.

To enable SSL/TLS, both Net::SMTP and Mail::Sender require IO::Socket::SSL and Net::SSLeay. Net::SMTP also requires Net::SMTPS.

If the default SMTP port of 25 doesn't work, you can configure it with the $cfg->{smtpPort} option, usually to 465 or 587.

Bounced Emails

A good many of the emails the forum software sends will bounce back for various reasons, the main reasons being misspelled addresses, addresses that become invalid, jammed mailboxes and as of late also spam filters that fake bounces when they erroneously classify forum emails as spam.

The simplest solution for handling these would be routing all emails that go to the forum email address to /dev/null or ignore/delete them some other way. But that way, you will send email subscriptions to dead and jammed mailboxes over and over again, which probably violates any number of RFCs, unwritten netiquette rules and maybe badly written anti-spam laws. Last not least it wastes your server's precious resources.

Since the task of recognizing MTA bounce formats is better left to Sisyphus, mwForum has the following simpler solution:

  1. All emails are sent with an existing sender address that nobody should normally send email to, such as noreply@example.com or bounce@example.org
  2. An X-mwForum-BounceAuth: 123456789 header is added to all emails, where the number is a static random number taken from the user's database record. Since this value is secret, bounces can't be faked by malicious users.
  3. All emails arriving for above email address get retrieved via POP3, and are checked for the header which is mirrored in most (but unfortunately not all) of them, and if there's an auth value matching an existing user's, a bounce counter is incremented in the user's database record. If there's no matching auth value, the email is ignored and gets deleted.
  4. If the bounce counter reaches configurable treshold values, countermeasures are taken, like warning the user, canceling subscriptions and email notifications or disabling all email sending to that user (users.dontEmail flag).
  5. Every day during the cronjob run, users' bounce counters are decremented. If the treshold for disabling email sending is reached, email sending to that user is re-enabled. Every received bounce internally counts as 3, whereas the daily decrement is by 1.

The POP3 part of the code requires the Mail::POP3Client Perl module and is located in cron_bounce.pl. It needs to be installed similarly to the main cronjob.

If the POP3 account is on a remote host and you want to protect the password, you can use password hashing via APOP or CRAM-MD5. Set the $cfg->{bouncePopAuth} option to the value supported by the POP3 server. CRAM-MD5 requires Digest::HMAC_MD5. Or you can use SSL/TLS, which is activated via the bouncePopSsl option. SSL/TLS requires IO::Socket::SSL and Net::SSLeay.

If you have problems with POP3, you can enable debug output by setting the $cfg->{bouncePopDbg} option to 1 and then start cron_bounce.pl from the command line. Finally, you can set the POP3 port and timeout values with the $cfg->{bouncePopPort} and $cfg->{bouncePopTout} options, respectively.

Bounces usually go back to the address specified in the Return-Path header field, not the From header field. If you use any of the SMTP mailers, both headers should be the same, but if you use the sendmail mailer, you might have to add an -f parameter to $cfg->{sendmail} so that it looks something like "/usr/sbin/sendmail -oi -oeq -t -f bounce\@example.com". This might not work everywhere.

OpenPGP Emails

With this feature users can upload their public keys to have their forum emails encrypted and signed. This text assumes that you're familiar with (Open)PGP and GnuPG.

Installation

  1. Make sure GnuPG is installed and working.
  2. Create a dedicated key used for signing all outgoing emails in the default keyring of the webserver's account. Since you'll have to add its passphrase to the configuration file, it's not exactly 100% safe, so never use an important key for this. Post the public key somewhere in the forum so that interested people can import it into their keyrings.
  3. Install the IPC::Run module (I would have preferred to use a smaller alternative like backticks or IPC::Run3, however this was the only method of running GnuPG and capturing the output that I got to work under Apache 2.0).
  4. If you haven't used an attachment directory before, set it up following the instructions. The keyrings for users will be stored in an automatically created subdirectory.
  5. Configure the forum key ID and passphrase with entries like "$cfg->{gpgSignKeyId} = '0x12345678';" and "$cfg->{gpgSignKeyPwd} = 'secret';" in MwfConfig.pm.
  6. To test if the installation works, go to the OpenPGP page (user_key.pl) linked from your profile page. Upload your public key and check the keyring output below the upload form. Set your key ID to enable encryption and then trigger an email, e.g. by using the Forgot Password feature on the login page.

Notes

If your gpg executable is not in the PATH, or the wrong version is found first, you can specify the one to be used by adding something like "$cfg->{gpgPath} = "/usr/local/bin/gpg";" to MwfConfig.pm.

Sometimes you might have to set additional options for GnuPG. Most of them can be put into the default configuration file of the webserver's account (e.g. /home/apache/.gnupg/gpg.conf). If you have to set the GnuPG home directory for some reason (e.g. if GnuPG doesn't see the HOME environment variable and therefore won't find its config file either), you can add it to MwfConfig.pm like this: "$cfg->{gpgOptions} = [ "--homedir" => "/home/foo/.gnupg" ];".

If signing or encrypting fails, emails will be sent anyway, and an error message will be logged.

The feature is currently untranslated. I may translate it if any non-English forums are actually using it.

Mass-Emailing

A feature that is sometimes requested but intentionally not implemented as such is mass-emailing of all registered users. The reason it's not directly available is that not giving users a possibility to opt-out is just a bad idea, and against anti-spam laws in many countries.

However mwForum already has email subscription features, so there's no need to reinvent the wheel. With the help of a little plugin, we can automatically subscribe new users to a dedicated announcement/newsletter board, and with a one-time manual SQL query, we can subscribe any existing users. This approach requires a little bit more technical expertise than a ready-made mass-emailing form, but leaves users the option to unsubscribe, and the admin the option to automatically unsubscribe users that have been absent for a long time. Other mwForum subscription features inherited are bounced email handling and excluding users with broken email addresses or accounts. The plugin would look like the following:


package MwfPlgSubscribe;

sub subscribe
{
  my %params = @_;
  my $m = $params{m};

  if ($params{level} == 1 && $params{entity} eq 'user' && $params{action} eq 'register') {
    $m->dbDo("
      INSERT INTO boardSubscriptions (userId, boardId) VALUES (?, ?)", $params{userId}, 42);
  }
}
1;

The example value "42" would have to be replaced with the board ID of your actual announcement board. Put the above into a file named MwfPlgSubscribe.pm in your script directory, and put a line "MwfPlgSubscribe::subscribe" into your logPlg forum option.

The SQL to subscribe any existing users to that board would look like the following. Again, replace "42" with the actual board ID. The SQL has to be fed to the database using the usual means, e.g. the "mysql" command line client, phpMyAdmin or whatever is available. PostgreSQL users will have to slightly alter the query.


REPLACE INTO boardSubscriptions (userId, boardId) (SELECT id, 42 FROM users);

If you don't already use a cronjob to send email subscriptions, you can trigger the sending of any new posts in your announcement board by either starting the cron_subscriptions.pl script from the command line, or by clicking the Subscriptions button on the cron_admin.pl page, though the latter isn't recommended if you have many users, as the email delivery may take time and a CGI script may time out.

Atom/RSS Feeds

mwForum supports Atom 1.0 and RSS 2.0 feeds. The feeds are static files generated by the cron_rss.pl script. It should be called every few minutes as a cronjob like in this example:


*/5 * * * *   user   cd /usr/local/apache/forum && perl cron_rss.pl

The files are written to an "xml" subdirectory of the attachment directory. If you haven't used an attachment directory so far, set it up following the instructions. Also make sure you have configured the scriptUrlPath forum option, otherwise the links in the feeds will be wrong.

The feeds could also have been implemented as a script that dynamically generates the feed content on every request, but this way a lot of performance is saved if many users request this file regularly (as feed readers tend to do). If there haven't been any new or edited posts since the last cronjob run, no new file will be written. This allows the webserver to send the appropriate 304 response for unchanged files, saving bandwidth.

For private boards (limited read access), no feeds will be generated. Posts in moderated boards that still await approval will not be included in feeds either.

You can put the hidden forum option "$cfg->{rssDiscovery}= 1;" into MwfConfig.pm to have the forum print <link> elements in the HTML header that make browser address bar icons for feeds appear. Widely used by other websites, but not recommended by me, as there are various stupid browser extensions that always fetch every linked feed on every page request.

CSS Styling

mwForum's HTML is valid HTML5, rendered in standards compliance mode where available. All styling is done with CSS.

Admins can install multiple styles (consisting of a CSS file and images), from which users can select one. Users can also independently select their font face and size (with correctly done styles at least).

Check the support forum for more discussion and questions about this topic. There's also a topic that lists changes to the default style between releases. If you create your own styles, and they're not completely site-specific, please share them.

There are three principal ways to customize the forum design:

Include your own CSS file with the forumStyle option which overrides some of the default style declarations

Advantages: Your customizations survive upgrades without extra manual work.

Disadvantages: You can't provide separate customizations for multiple styles. Making major changes by overriding can be inconvenient.

Append your customizations to the default and/or third-party styles

Advantages: You can provide customizations for multiple styles.

Disadvantages: You have to re-append your customizations to the styles after forum upgrades. Making major changes by overriding can be inconvenient.

Change mwForum/third-party styles completely, creating your own style

Advantages: If you want to seriously customize the design (do more than just change a few colors), overriding/resetting default/third-party styles might be inconvenient if not impossible, so this might be the only way to do major changes.

Disadvantages: Upgrading can be somewhat labor-intensive if new mwForum versions include their own major style/class changes, but that shouldn't happen too often, and pure style changes (no new or changed classes) can often be ignored.

Style Installation

Styles and their dependent files live in subdirectories of the data directory. The name of the subdirectory needs to be the same as that of the respective stylesheet file without the ".css" extension.

To install a new or downloaded style, make a copy of the "default2" style directory with its contents and name it something else, e.g. in the case of the downloaded Rybka styke, name it "rybka". Then copy all the files of your new or downloaded style into that directory, overwriting any files with identical names. There may be a few unused files in the new directory left after that, but that doesn't matter.

Add a line like "Name=identifier" to the styles forum option, enabling users to select the style. "Name" is the name visible to users on their options page. It can be freely chosen (incl. non-ASCII characters), but should not be changed once it has been selected by users, as it's what is stored in the users table of the database (design error, sorry). "identifier" is the name of the directory and CSS file without extension.

Style Snippets

In addition to the methods above, you might want to give your users more individual display options, options that may not always warrant hardcoded changes to the code and database layout. Enter the Style Snippets feature. You can install CSS snippets like this:

  1. Add to the styleSnippets forum option lines with id=cssrule pairs like in the following example, which moves avatar images to the left side of posts: "styAvaLft=div.pst img.ava { float: left; margin: 0 5px 0 0; }".
  2. To associate readable and language-specific descriptions with the rule IDs, use the Local Strings feature described above. For example, put the following line in MwfEnglishLocal.pm: "$lng->{styAvaLft} = 'Avatar on left side';".

Users will now see a new display option checkbox: "[ ] Avatar on left side". If they enable the option, every generated page will contain the "div.pst img.ava { float: left; margin: 0 5px 0 0; }" rule in the inline style element.

Rule IDs must start with "sty", and have max. 10 characters.

Style rules and options are ordered by ID, so you might want to reserve the fourth character of the ID for ordering purposes, if your rule order might become relevant.

Style Options

There are currently four style-specific options that can be set with the styleOptions forum option. This feature is somewhat obscure and most admins will probably just ignore it, but it's there if you want full control.

excludeUA can be used to signal that a style won't work properly with certain browsers, e.g. setting the option to "MSIE (?:5|6)" will prevent the style from being used in legacy versions of Internet Explorer. The value is a Perl regular expression that will be matched against the User Agent string of the browser. includeUA is the opposite and signals that a style requires one or more specific browsers.

buttonIcons must be set to "1" to enable the display of button icons (see below) with the style. This doesn't happen by default as the icons don't necessarily look good with every style out there.

Setting wrapperDivs to "1" enables three additional <div> elements around the main part of every page. This is used by some styles for effects like block shadows.

The overall configuration format of style options is shown in the examples on the forum options page. If you're installing someone else's style, any options required or recommended for that style should be listed near the top of the CSS file (the forum doesn't read the options from that location itself for performance reasons) and should be copy&pasted to the forum options page.

Button Icons

For the button icons to be visible to a user, the buttonIcons style option must be enabled per installed style by an admin, and the user's "Show decoration" option must be enabled as well.

Unlike the other icons in mwForum, the button icons are shared between all installed styles, because it doesn't seem likely that anyone will come up with an alternative, style-specific set of icons, given their number.

CSS Sprites

Since version 2.19.0, mwForum uses the CSS Sprite technique, which in short means combining many small images into bigger images to save on the considerable overhead of HTTP requests for many small files. If you want to modify the affected images for your own styles, you will need to understand and use this technique. If you're not already familiar with it, you can probably get the background by googling one of the many available articles, here's just a quick list for the steps I use to create the sprite images and CSS rules:

  1. Go to the CSS Sprite Generator website
  2. Zip all style images that are not backgrounds together and select that archive for upload
  3. Set the offsets to "1" and "1", no need to waste space
  4. Check "Compress Image with OptiPNG"
  5. Set "Class Prefix" to "sic_"
  6. Submit
  7. Copy generated CSS to your style
  8. Add a ".sic" rule (e.g. as in Default2) with at least "width", "height" and "background-image: url(icons.png)"
  9. Download the generated csg-*.png, put it in your style directory and rename it to "icons.png"

You can of course also modify the combined images, but keeping the original small images and regenerating the combined images can be more convenient, depending on the situation. You can also keep any of your own additional images separate, only the built-in icons need to be sprites.

Custom Style Tag

If you want to add additional inline formatting styles that can be applied to text with [tags], for example colors, strike-through or the like, you can use the Custom Style Tag feature. It's simpler to use for admins than creating a display plugin with similar functionality. It's also faster, and doesn't break the HTML validity like most naive display plugins will usually do, given random user input. The feature doesn't allow anything fancy like structured nested tags ("<ul><li></li></ul>") or semantic HTML though.

Available custom styles for the tag and their CSS declarations are defined in the customStyles forum option as shown in the accompanying examples. The markup to be used in posts looks like the following:


PHP [c=strike]rules[/c]sucks

Admittedly not the most compact markup, but using a common tag name for all custom styles has performance and other internal advantages that something like "[strike]blah[/strike]" doesn't, and it doesn't collide with unrelated tag types or other uses of brackets.

There's also a forum option to enable tag insertion buttons for either the generic [c=][/c] syntax where the user would have to type in the style name manually, or separate buttons for all available custom styles. It's possible to create an Include plugin that provides tag insertion buttons for whatever specific custom styles the admin might consider worthy.

GeoIP

A GeoLite Country database (commonly called GeoIP by its original shorter name) supplies IP-address-to-country mapping for forum features like country flags and Google Maps support. A user's location is determined by looking up the user's IP address in the database, which is a binary file, not a slowish web service or DNS-based system. The location will of course not always be correct.

mwForum can access a GeoIP database through either the Geo::IP module, which is written in C and therefore very fast, but needs a compiler and also a separate C API library installed, or the Geo::IP::PurePerl module, which is written in Perl and therefore slower, but can be installed where C libraries and modules can't be installed.

The GeoIP license mandates inclusion of this acknowledgement in mwForum's documentation: "This product includes GeoLite data created by MaxMind, available from https://maxmind.com/". Note however that mwForum doesn't actually include any of the data or source code, it only uses installed databases and modules.

Installation

  1. If you want to use the C library and C-based Geo::IP module:
    1. Download and install the C library from the GeoIP site following its instructions. This will also install the database file GeoIP.dat.
    2. Install the Geo::IP module.
    If you have to use the Geo::IP::PurePerl module:
    1. Install the Geo::IP::PurePerl module.
    2. Download the GeoIP.dat.gz database file from the GeoIP site, ungzip it and put it somewhere.
  2. Set the geoIp forum option to the filesystem path of the database file.

The database file is updated monthly, so you might want to fetch those updates every now and then.

GeoLite City

mwForum can also use a GeoLite City database, which includes city and region information (which is, naturally, less accurate than the country information). This database is a lot bigger (around 42MB, vs. around 1MB for GeoLite Country), therefore I wouldn't generally recommend it for a forum with limited resources. The feature is configured simply by setting the forum option to the path of the GeoLiteCity.dat file instead of the GeoIP.dat file. "City" must be part of the filename for mwForum to recognize the different format. Before you use the City database, you might want to inform your users and mention the Privacy user option that hides the IP-based location, as well as set the default of the Privacy option to enabled.

Country Flags

The Country Flags feature, when enabled, displays users' country flags and the country names as tooltips of the flag images, on topic and user info pages. To enable the feature, set up a GeoIP database as explained in the previous section, and check the userFlags forum option. The optional city/region information is only shown on user info pages, not on topic pages.

Now you need to obtain flag images. At the time of this writing, there are two sets of world flags with 18x12 pixels at this site (broken link). Another set with 16x11 flags can be found at this site (broken link), and is recommended. If you want to use flags in other sizes than 16x11, you need to copy the flag CSS rules from mwforum.css into your own CSS file included with the forumStyle forum option, and modify the height/width declarations. Any images in GIF format need to be converted to PNG format. Put the flag images into a subdirectory of your data directory called "flags".

Google Maps

The map feature shows Google-powered maps on user info pages when location info is available, and is enabled with the userInfoMap forum option.

If the Location profile field is filled in, that data is used to determine the location. If GeoIP country information is available using the feature explained above, it is sent along to help resolve ambiguities (e.g. when only a city is named but exists in multiple countries). If the field contains garbage or inadequate data, the map won't be shown. If the field is empty and the user's Privacy option isn't checked, the GeoIP-based location is used directly instead.

The linked title above the map shows the "formatted address" as determined by Google, shown in the browser's language. Clicking it will zoom the map to the viewport recommended by Google for that location (if the location only consists of a big country, it may actually zoom out). There is also a marker on the map pointing at the location, which will just be the middle of the country, city or other area if no exact street address is given.

If Google finds multiple matches for the location info, the first match will be centered and shown in the title, but other locations will also be highlighted with markers, which may be outside of the current viewport. The markers have tooltips with the address of the matched location. Users wanting one exact match can simply test their address specifications with the search field on the normal Google Maps site.

Please observe the Google Maps API Terms of Service (summed up in the FAQ). The feature uses the v3 API which doesn't require an "API key" like earlier versions.

Quoting

mwForum uses traditional email-style inline quoting, where quoted lines are prefixed with greater-than (>) signs. Users can always manually copy and paste and then prefix lines from the parent post with > characters to highlight them with light grey color (or whatever the selected style does with the quote text). This includes very long lines that will be wrapped to paragraphs by browsers at display time.

Ideally users should only copy short parts of the parent post to make clear what they're referring to in their answer. Since the parent post is usually not far away in a nested/threaded forum like mwForum, it's not necessary to quote huge tracts of original text for reference. That unfortunately common practice just bloats topics, making them hard to read and clogging up the database.

However, since mwForum also supports flat, non-threaded topics, there's an optional automatic quoting feature. It requires the Text::Flowed Perl module, and works just like the automatic quoting in most email applications (at least when they're not used in HTML mode). It actually works somewhat better than many email apps, since it can properly reflow multi-level quotes thanks to Text::Flowed, which was written by Philip Mak for mwForum.


>> Lorem ipsum dolor sit amet, consectetur 
>> adipisicing elit, sed do eiusmod tempor incididunt 
>> ut labore et dolore magna aliqua.
> Lorem ipsum dolor sit amet, consectetur adipisicing 
> elit, sed do eiusmod tempor incididunt ut labore et 
> dolore magna aliqua.

I don't recommend to actually use this feature, though. The Flying Spaghetti Monster simply hasn't designed humanity to properly use it, and topics will end up consisting of huge amounts of quoted text with one new line added below. Many people don't seem to mind, but I think it's really annoying. In any case, you can enable the feature for non-threaded boards only, or for both non-threaded and threaded boards. The latter option is recommended even less, since in very indented posts in threaded topics, quoted text, even when quoted with very limited line lengths, might be wrapped in the ugly "camel" style at display time (depending on the respective user's font and browser window size of course).


> Lorem ipsum dolor sit amet, consectetur 
adipisicing 
> elit, sed do eiusmod tempor incididunt ut 
labore et 
> dolore magna aliqua.

So what about [quote] markup tags instead? Historical reasons aside, copying, pasting and >-prefixing quotes is simply faster than bracketing quotes with tags. Javascript tag insertion buttons make using tags more convenient, but they're not perfect. In some browers like Opera, only very recent browser releases make those buttons work (more or less). In other browsers like MSIE, they've worked for longer, but have other problems. Also, since this method has been established in mwForum for so long now, changing it would be a major pain, as would be mixing of both methods.

FastCGI

I'm no FastCGI expert and have never used it before. But here's how I got mwForum to work with FastCGI and lighttpd, a nice lightweight webserver. I haven't tested that installation all that much, though.

Since mwForum has no FastCGI accept loop built-in, it needs a dispatcher script. An example on the lighttpd site served as a starter, but didn't work out of the box for mwForum, as CGI.pm was somehow screwing up package names or whatever. But I don't like that bloated piece of code anyway, so I've changed the script to use the FCGI Perl module directly. I also did a few other small changes, and the resulting script looks like this:


#!/usr/bin/perl

use lib "/usr/local/lighttpd/htdocs/forum";

use strict;
use FCGI;
use Embed::Persistent;

my $fcgi = FCGI::Request();
my $p = Embed::Persistent->new();

while ($fcgi->Accept() >= 0) {
  my $filename = $ENV{SCRIPT_FILENAME};
  my $package = $p->valid_package_name($filename);
  my $mtime;
  if ($p->cached($filename, $package, \$mtime)) {
    eval { $package->handler() };
  }
  else {
    eval { $p->eval_file($filename) };
  }
}

You need to adjust the "use lib" line to point to your script directory. Put the dispatch.pl script either into your forum script directory or somewhere else in your webserver's directories. I have no idea whether the script will work with other Perl FastCGI apps.

Then you need to install the FCGI Perl module. It requires compilation, but if you're using FastCGI, you hopefully already have it installed or at least know what you're doing.

Also required is the Embed::Persistent module. Now that one is positively ancient and its Makefile didn't work for me at all. But you can manually install the pure-Perl Persistent.pm file the usual way, e.g. by downloading it directly without the rest of the ExtUtils-Embed distribution from the link provided and by copying it into a subdirectory of your script directory called "Embed".

My lighttpd test configuration spawns a single Perl FastCGI process from lighttpd itself:


fastcgi.server = ( 
  ".pl" => (( 
    "socket"       => "/tmp/perl.fcgi.socket",
    "bin-path"     => "/usr/local/lighttpd/sbin/dispatch.pl",
    "min-procs"    => 1,
    "max-procs"    => 1,
    "idle-timeout" => 300,
  ))
)

The only lines I changed in the actual mwForum code to support FastCGI are a few instances of exit() that I replaced with die(), since exit() kills the FastCGI processes, but die() exceptions can be caught by the eval{} in the dispatcher.

OpenID

"OpenID is a free and easy way to use a single digital identity across the Internet"

mwForum can act as a OpenID Relying Party (aka OpenID Consumer). It will create a normal user account the first time someone logs in using their OpenID identity. The OpenID URL will be stored and displayed on the user info page as well as in a title tooltip for the username on topic pages. Optionally, the list of accepted OpenID providers can be limited with a whitelist, and registration/login can be limited to OpenID only.

Even though OpenID is billed as a lightweight protocol, the list of required Perl modules for this feature is unfortunately rather long, and includes modules that require compilation. The principal modules required are Net::OpenID::Consumer, Cache::FastMmap and LWPx::ParanoidAgent. Crypt::SSLeay is required for https-based URLs, which are common. The libgmp system library and Math::BigInt::GMP module are strongly recommended, as without them the performance of some of the involved cryptography would be quite bad. The modules listed have many module dependencies of their own, which I won't list explicitely. It's probably best to install the modules using the CPAN shell to semi-automatically install the dependencies. To complicate matters, some of the modules may fail their test suites even though they will work fine for our purposes, so you may have to use the "force install" command (this happened with URI::Fetch and HTML::Parser in my case).

Aside from installing the requirements, you need to activate the openId forum option and set the maxUserNameLen forum option to something bigger (maybe 40). The feature also requires the attachment directory for caching purposes. If you haven't used an attachment directory before, set it up following the instructions.

Notes

The supported OpenID protocol version depends on the version of the installed Net::OpenID::Consumer module. The current version at the time of this writing supports OpenID 2.0. Some providers such as Google and Yahoo require 2.0.

The username of OpenID users will either be the nickname transmitted via the Simple Registration protocol extension, or if there's no or no valid and unique nickname transmitted, an abbreviated form of the OpenID URL itself will be used. If even that is not unique, the full URL will be used as username. Users who end up with an abbreviated or full OpenID URL as username can rename themselves to something shorter by following the Name link on the user profile page (OpenID users with URLs as usernames get one more chance to change their name than other users, who get a configurable number of changes, 0 by default).

The Simple Registration protocol extension, if available from the OpenID provider, is also used to copy the full name, date of birth and country into mwForum's user profile.

On a mod_perl server, you might want to use advanced tricks to not execute user_openid.pl under mod_perl, as the number of required modules inflates the RAM usage a lot (on my forum-only server, it doubled the process size from 10MB to 20MB). The fact that the HTTP communication going on in the background may take several seconds would also make this advisable, at least for reverse proxy mod_perl setups.

Allowing multiple domain names for the forum might cause trouble (incl. "bad_mode" error messages). Even if your site has multiple domains, you should generally enforce (via redirection) a single domain for the forum, to avoid problems with this, cookies and whatnot.

LDAP

LDAP is supported in the form of an example login authentication plugin in MwfPlgAuthen::authenLoginLdap(). This plugin takes the username/password from the login page and validates them against an LDAP server. If the credentials are correct, and there is not yet an mwForum user account for that username, an account is automatically created and the user logged in. If an account already exists, the user is simply logged in as that user.

LDAP is only provided as an example and not a fully fledged plugin as the requirements of admins for all things authentication tend to be so different that it's hard to cover everything, especially as I've never actually employed LDAP myself. Therefore the example might need some modifications and additions to suit your specific needs.

Installation

  1. Install the Net::LDAP Perl module and its dependencies.
  2. Edit the $cfg->{ldap…} options in the MwfPlgAuthen::authenLoginLdap() function (optionally move them to your MwfConfig.pm). Their meaning is hopefully obvious to people familiar with LDAP.
  3. Put MwfPlgAuthen.pm into your script directory, after editing it to your needs and removing unrelated example functions.
  4. Put "login=MwfPlgAuthen::authenLoginLdap" into the authenPlg forum option.

Notes

The plugin example doesn't revoke access to the forum if the LDAP entry is deleted or changed. As long as the user has a valid cookie for the forum, they're still logged in. If forum access needs to be revoked, this could be handled by a cronjob that empties the mwForum users.password column periodically, or by some kind of hook script that is called from the site's user administration system and instantly wipes the password column.

SASL and SSL/TLS support would require a few changes/additions.

The code assumes that the LDAP directory has the user password as cleartext. If it doesn't, code would have to be added to do the hashing etc.

The only profile fields copied from the user's LDAP entry are the real name and email address. This could also be extended.

I've tested this example against a public LDAP server. As I didn't have access to any real passwords that way of course, I simply used some other attribute as password.

If you've checked this out, please provide feedback, whether it worked for you or not. People asking for LDAP on the support forum have made a habit of vanishing without giving any, leaving us none the wiser.

Captcha

"A CAPTCHA is a program that protects websites against bots by generating and grading tests that humans can pass but current computer programs cannot."

Captchas are used to prevent spambots from registering and/or posting. With the captcha forum option, captcha input fields or images can be added to various forms. There are several different captcha implementations with varying degrees of complexity.

It should be noted that at the time of this writing and as far as I know, only mwForum installations that allow unregistered posting have been spammed by bots. No forums that require registration with email verification (the default) have received any spam by bots, only manual spam by human beings, which can't be stopped by captcha. While some bots do register accounts, they never seem to receive and evaluate the registration emails. So, if you don't allow unregistered posting or email-less registration, you may not have to deal with any of this. You might want to set the acctExpiration forum option to a few days though, to get rid of unused bot accounts.

Invisible Honeypot Field

This simple and convenient captcha adds a text input field that is set to be invisible to humans by means of CSS (unless some exotic or crappy browser is used). Since no known spambot interprets CSS, and most of them have the habit of filling in every text field, most bot submissions can be weeded out easily. Since there's no caption for this field, even humans with exotic non-CSS browsers should hopefully leave it empty.

Question/Answer

This simple captcha let's you pose a question, usually something to do with your forum, which the user must answer. The question and answer must be configured in MwfConfig.pm as seen below. The answer is case-independent. Don't make it a math question like "What's 1+2?", spambots might be on to these. Giving strong hints like the first and last characters of the word should be ok, though.


$cfg->{captchaQuestn}  = "Anti-spam test: What's this forum about?";
$cfg->{captchaAnswer}  = "Chess";

GD::SecurityImage

This captcha implementation displays randomized images with characters that need to be copied into a text field, as seen on many other sites. The random quality of the images is not that great due to the limited functionality of the used GD::SecurityImage and GD Perl modules, but then any smart OCR spambots are probably only going to be written to attack high-volume sites by the major web corporations.

Aside from installing the named modules and setting up the attachment directory for the generated image files, you also need to specify a TTF font file in MwfConfig.pm as shown below. Not all fonts will work equally well, one free font available that works is "VeraBd.ttf" (google for it). This method is not accessibility-enabled in any way.


$cfg->{captchaTtf} = "/usr/local/share/ttf/VeraBd.ttf";

reCAPTCHA 1.0 Service

This captcha implementation uses the reCAPTCHA web service by Google. This version is obsolete and may be removed by Google before long. It's only included for existing installations that don't have the required SSL/TLS Perl modules required to upgrade to version 2.0.

To make this work in mwForum, you need to have the HTTP::Tiny (included with Perl 5.14 and later) or LWP::UserAgent (aka libwww-perl, and its many dependencies) Perl modules installed. You also need a Google account and put the public and private keys you get from the linked site into your MwfConfig.pm:


$cfg->{reCapPubKey} = '01234567890abcdef';
$cfg->{reCapPrvKey} = '01234567890abcdef';

reCAPTCHA 2.0 Service

The new version of Google's reCAPTCHA web service.

Similarly to the 1.0 version, you need to have the HTTP::Tiny (included with Perl 5.14 and later) or LWP::UserAgent (aka libwww-perl, and its many dependencies) Perl modules installed. Since Google requires SSL/TLS for this version, IO::Socket::SSL and Net::SSLeay are required, too. You also need a Google account and put the site and secret keys you get from the linked site into your MwfConfig.pm:


$cfg->{reCapSiteKey} = '01234567890abcdef';
$cfg->{reCapSecKey}  = '01234567890abcdef';

Akismet Service

Akismet by Automattic isn't actually a captcha, it's a web service that does spam classification based on post text and metadata. Check their site for details and terms of service. In mwForum, Akismet is used for posts and private messages, but not registrations. It's currently experimental, and it's not yet known how reliable their classification is (which will no doubt change over time anyway).

To enable it in mwForum, you need to install the LWP::UserAgent Perl module (aka libwww-perl). Register an account at the WordPress site, and put the API key you get from them into your MwfConfig.pm, e.g.: "$cfg->{akismetKey} = '0123456789ab';".

DNSBL Service

Not really a catpcha either, DNS-based blacklists are frequently used for email spam filtering. Some blacklists also provide IPs used for web-spamming and/or belonging to open proxies. This method is also experimental, and usefulness very much depends on the used blacklist, which may not fare too well against botnet-based spammers. Advantages of this method are that it doesn't pester users (unless they're hit by blacklisted dynamic IPs), is very light on resources and doesn't require the installation of any Perl modules. To enable, put the domain name of the used blacklist into MwfConfig.pm, e.g.: "$cfg->{dnsbl} = 'opm.tornevall.org';".

Cookies

Possible reasons for various cookie problems that can prevent successful login/logout:

To really understand the details of these problems, one would need to be familiar with the details of the cookie specification. And since people familiar with it won't need this FAQ entry, I won't go into anymore detail here.

Search Engine Optimization

This topic is about improving the index coverage and resource consumption by external search engines like Google, not about the built-in forum search.

mwForum's URLs are by default not very search engine friendly. Forums may still get indexed more or less completely, but spiders request a lot more pages than necessary, and the search results contain all sorts of useless or suboptimal links, like variations of the same page with different parameters in the URL.

It's not an option for mwForum to use a completely different URL style by default, with most admins being limited to ancient CGI setups and having limited control over their servers, and with the current scheme being established for many years. You can however improve the indexing behaviour a lot by using a few extended entries in the site's robots.txt file. This can be done by just about anybody with some basic webmastering knowledge, and prevents most useless page requests and bad search result links. The extended syntax used is not supported by all search engines, so far I know about support by Google, Bing and Yahoo. But that covers almost all of the search market. Don't forget to adjust the script path as necessary:


User-agent: Googlebot
Disallow: /cgi-bin/mwf/
Allow: /cgi-bin/mwf/forum_show.pl$
Allow: /cgi-bin/mwf/board_show.pl?bid=
Allow: /cgi-bin/mwf/topic_show.pl?tid=

Archiving

Archiving is one of those topics where everybody has a different idea on how it's supposed to work, and of course there are different purposes for it to begin with (backup, performance, etc). The approach taken by mwForum's built-in archiving is aimed at improving performance by moving out old topics and obsolete boards, while still keeping them readable through the normal user interface. It's also designed for minimal code impact.

Archived boards, topics and and posts are moved to separate archive database tables. None of the other entities and relationships (e.g. polls, attachments and memberships) get archived, though. If the forum is browsed in archive mode, those three archive tables are queried instead of the normal ones. All other tables (users, categories etc.) are used as normal. Archive contents are read-only, no other operations can be performed on them.

To have expired topics moved to the archive tables by the main cronjob, enable the archiveExpired forum option. To archive a complete board, use the Archive link on the board page admin bar, and delete the original board afterwards.

To start browsing the archive, append an "arc=1" parameter to URLs, as in "forum_show.pl?arc=1". The paramater will stick while you browse, so to leave archive mode again, you would have to manually remove it, or just reenter the forum through a normal bookmark. Only the forum/board/topic pages and the forum search page actually use the archive contents. There's no regular link to the archive mode anywhere in the user interface, so if you want normal users to be able to find it, post a link in an announcement post or the like, and use the opportunity to also explain the archive limitations to your users.

The fact that other non-archive tables (categories, users, memberships etc.) are used normally while browsing in archive mode has the following consequences:

Notes

If you really need any of the other stuff that is not archived resp. not enabled for archived topics and posts, this feature isn't for you.

The original and archive table schemas need to remain in sync regarding the number and order of columns. If you add any columns of your own, you need to take this into account.

This feature doesn't work with SQLite.

Misc. UI Issues

Collapsing/Expanding

Categories on the forum page and topic branches on the topic page can be collapsed and expanded. This feature is implemented using DHTML, as server-side tree controls are nasty bandwidth and CPU hogs.

"Auto-collapsing" can be activated as a user option and automatically collapses topic branches without new or unread posts, but only if there are new or unread posts in that topic at all. If there are none at all, the user probably wanted to re-read the old posts.

Post Header Backgrounds

The colors and order of precedence are style-dependent, but the latter should be the same as listed:

Edit Timestamp

When a post is edited within two minutes of posting, no timestamp is shown, so there's time to correct small errors. If a post is edited after that, an edit timestamp is shown. If an edit is new since the user's last session, the timestamp is emphasized (bold red in Default2, as yellow would be unreadable).

Limitations

The parts of the user interface that are only used by administrators are not translated. This is intentional as getting people to translate is hard enough without the job being at least three times as much work.

Due to the threaded nature of mwForum, huge topics (more than a few hundred posts) are not particularly efficient. The software always needs to retrieve the basic data for all posts in a topic to build the tree structure that's required to see which posts are on the current page of the topic. While mwForum also supports non-threaded flat boards, they use mostly the same code and are therefore no more efficient than threaded boards. It would be possible to create a topic_show.pl script specifically for non-threaded boards that would be efficient with any number of posts, but as threaded mode has always been the focus of this project, no such script is being maintained.

There is no HTML templating support. The main reasons are that such a thing would require a major rewrite, that templates wouldn't be version-independent like some people seem to think, that there would be a performance impact because the page contents couldn't be streamed out anymore, and last not least that even for the much more simple CSS styling few enough people ever shared and maintained styles.

Browser Cache Refreshing

Any admin who has been keeping mwForum up-to-date has gotten the usual complaints after forum upgrades by users who haven't discovered the magic F5 key to refresh their browser cache yet, and like me, they have (probably) also been too lazy to recommend F5 usage in advance of upgrades. Furthermore, if a file timestamp gets downgraded (e.g. because of a reverted change), Ctrl+F5 or emptying the cache is required, depending on the browser in question.

The HTTP expiration header functionality is useless to solve this problem, as nobody ever knows in advance when something might change in the future.

The simplest way to handle this in theory is to use different filenames for new versions of files. But unless some kind of CMS is used that handles this, it's not very practical.

Another solution to force browsers to reload fresh copies of files is to append a versioned query string to the URLs of static files, e.g. "/mwf/forum.css?v42". This is often recommended by various articles on the topic, but they tend to ignore the fact that browsers won't really cache files anymore if their URLs contain question marks, or more specifically they will keep sending If-Modified-Since/304 requests on every page hit. In addition, the bigger problem with this approach is that nowadays lots of images etc. are referenced from CSS and JS files, where it's impractical to insert dynamic version numbers.

mwForum supports adding a serial version number to the data URL path instead, e.g. "/mwf/v42/forum.css". This approach also affects files addressed with relative paths (e.g. only filenames) from stylesheets and scripts. The added path component needs to be written out on the server side, which unfortunately means not everybody may be able to use it. Examples using mod_alias and mod_rewrite on Apache (only one is required):


AliasMatch ^/mwf/v\d+/(.+)  /usr/local/apache/htdocs/mwf/$1

RewriteRule ^/mwf/v\d+/(.+)  /mwf/$1

To enable the feature in mwForum, set the dataVersion forum option to 1. When you upgrade the forum, upgrade.pl will automatically increase this number, if it's not 0. When you change CSS, JS, image etc. files on your own, you can also increase the number by double-clicking the copyright message at the bottom of all pages (this currently uses AJAX, mostly just to serve as an AJAX example and test case).

Cache Optimization

Now that we can force browsers to load changed files when we want, we can optionally also get them to cache unchanged static files for longer. This is especially useful for recently changed files, which browsers will also give the If-Modified-Since/304 treatment for a while. Example:


<LocationMatch ^/mwf/v\d+>
  ExpiresActive On
  ExpiresDefault "access plus 30 days"
</LocationMatch>

Mobile Browsing

A few tips to make a forum more accessible via mobile browsers on small screens:

Moderation Shortcuts

Branch Moving

Admins and board moderators can Ctrl-Click a topic branch's base post and Alt-Click the new parent post to move a branch within a topic. This is much faster than the numeric post ID input on the branch_admin page.

Image Transfer to Attachment

Admins and board moderators can transfer remote images embedded with the [img] tag to a post attachment by Ctrl-Clicking the image. The "[img]http://example.org/example.png[/img]" markup will be changed to "[img thb]example.png[/img]", so the image will be shown in the same place in the post, and as a thumbnail if possible. Users of this feature need to keep legal issues in mind.

Post attachments need to be set up of course. The $cfg->{maxAttachLen} forum option is enforced, but can be overriden with $cfg->{maxAttXferLen}. The feature requires either the HTTP::Tiny (included with Perl 5.14 and later) or LWP::UserAgent (aka libwww-perl, and its many dependencies) Perl modules installed. To support SSL/TLS for https URLs, IO::Socket::SSL and Net::SSLeay are required.

The transfer only works for unlinked images (i.e. not for "[url=http://foo][img]http://bar[/img][/url]"), as linked images will usually be just thumbnails, and because linked attached images don't work in mwForum for technical reasons.