<MIVA standardoutputlevel=""></MIVA>
<MvASSIGN NAME="OFF" VALUE = "{'<MIVA standardoutputlevel=\"\">'}">
<MvASSIGN NAME="ON" VALUE = "{'<MIVA standardoutputlevel=\"text,html\">'}">
<MvCOMMENT>###################################################################
# Message Board Version 1.1 #
# Developed by Michael Sussna Yahoo! #
# Created 12/11/97 Last Modified 9/28/99 #
# Based on WWWBoard #
# Copyright 1996 Matt Wright mattw@worldwidemart.com #
# Scripts Archive at: http://www.worldwidemart.com/scripts/ #
##############################################################################
# WWWBoard COPYRIGHT NOTICE #
# Copyright 1996 Matthew M. Wright All Rights Reserved. #
# #
# WWWBoard may be used and modified free of charge by anyone so long as #
# this copyright notice and the comments above remain intact. By using this #
# code you agree to indemnify Matthew M. Wright from any liability that #
# might arise from it's use. #
# #
# Selling the code for this program without prior written consent is #
# expressly forbidden. In other words, please ask first before you try and #
# make money off of my program. #
# #
# Obtain permission before redistributing this software over the Internet or #
# in any other medium. In all cases copyright and header must remain intact.#
##############################################################################
# Yahoo!, Inc. COPYRIGHT NOTICE #
# Copyright 1998-1999 Simple Network Communications, Inc. #
# Copyright 1999-2000 Yahoo!, Inc. #
# All rights reserved. #
##############################################################################
# #
# CHANGE LOG #
# #
# Version 1.16, 8/01/01: #
# #
# Added additional checks for executable code being submitted in form #
# fields. #
# #
# Version 1.15, 8/16/00: #
# #
# Added additional checks for executable code being submitted in form #
# fields. #
# #
# Version 1.14, 6/7/00: #
# #
# Added additional file locking logic to Remove and Compress Database #
# functions. #
# #
# Version 1.13, 9/28/99: #
# #
# Added MvLOCKFILE to Compress Database function. This should prevent #
# potential conflicting write accesses to the database during a compress. #
# #
# Version 1.12, 2/11/99: #
# #
# Changed default year in search to be current year. #
# #
# Version 1.11, 12/24/98: #
# #
# Added URL information in post and followup preview. #
# #
# Version 1.10, 12/18/98: #
# #
# Added "In reply to" feature when viewing a message. #
# Changed access of messages to use MvFIND, significantly speeding access. #
# Added wildcard to IP blocking in Admin. #
# #
##################################################################</MvCOMMENT>
<MvCOMMENT> Define Variables </MvCOMMENT>
<MvASSIGN NAME = "mesgdir" VALUE = "{'messages'}">
<MvASSIGN NAME = "passwd_file" VALUE = "{'passwd.txt'}">
<MvASSIGN NAME = "config_file" VALUE = "{'config.txt'}">
<MvASSIGN NAME = "unwelcome_list" VALUE = "{'unwelcome.txt'}">
<MvASSIGN NAME = "unwelcome_list2" VALUE = "{'unwelcome2.txt'}">
<MvASSIGN NAME = "ext" VALUE = "{'txt'}">
<MvCOMMENT> This is a special self number in the database: </MvCOMMENT>
<MvASSIGN NAME = "super_root" VALUE = "{'9999999'}">
<MvCOMMENT>
The message board is displayed in a specific sequence. The most recent
main postings (or roots) are displayed first. Within each main posting
any followup postings are then displayed, most recent first. Followups
to followups are displayed most recent first, and so on. This produces
a hierarchy of postings.
To build this display as fast as possible from the database, each posting
is assigned a sequence number. The sequence numbers are designed so that
the hierarchy follows from simply listing the postings in sequence. For
example, suppose that there are 10 postings that should display in the
following hierarchy:
4
10
5
8
9
6
7
3
1
2
The main postings are 1, 3, and 4. 4 is the most recent main posting.
Posting 4 has 2 followups, 5 and 10. 10 is more recent than 5 and has
no followups. 5 has two followups, 6 and 8, each of which has a followup.
The numbering of the postings reflects the order in which they were
created, 10 being the most recently created posting.
To simply list the postings so that they would display in the proper
order above, we would need the following relationships between any
sequence numbers assigned: 4's sequence number should be first in the
list, followed by 10's, 5's, 8's, 9's, 6's, 7's, 3's, 1's, and finally
2's.
We could assign ascending sequence numbers, but we actually assign
descending numbers. So, in our example, we could think of the sequence
number assignments as 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 for postings
4, 10, 5, 8, 9, 6, 7, 3, 1, and 2 respectively.
There are several constraints which influence our scheme for sequence
numbering:
Each posting should have a unique sequence number --- we want to avoid
duplicates so that there is no ambiguity when listing postings.
Each sequence number should correctly reflect the relative position of its
posting in the hierarchy --- thus it should fall between the sequence
numbers of the postings between which the new posting is being inserted.
Insertion can occur at any arbitrary point in the hierarchy. We cannot
know ahead of time how many postings will be placed at any given location.
Finally, we need to allow for a reasonably large number of levels of
followup. In actual practice it is not unusual to see 20 levels of
followup. That is, one main posting could have a followup which could
have a followup which could have a followup, and so on, to 20 levels of
followup.
To accommodate these constraints, we have devised the following scheme
for sequence numbering. Unique numbering is supported by careful planning
for handling the other constraints. Reflecting relative position in the
hierarchy is provided by selecting a sequence number for a new posting
that lies between the sequence numbers of the postings before and after
the new posting.
By assigning each root a comfortable range of numbers for its followups,
we lay the foundation for satisfying the final two constraints. Suppose
that we allow 1000 followups per root. So the roots are numbered 1000,
2000, and so on as they are created. Followups are numbered sequentially
from 1 within that range. So root 2000's followups fall in the range
1001 - 1999, since posting 2000 must display before any of its followups.
The first followup gets 1001. If a second followup to posting 2000 is
created, it gets sequence number 1002. But what if a followup to posting
1001 is created? It needs a sequence number lower than 1001, since it
must display after 1001, right? What if we renumber so that what was 1001
becomes 1002, and the new posting gets 1001? This is the scheme that we use.
As a followup is inserted, it causes a renumbering of all followups between
it and the root. So suppose we have 1001 - 1005, and need to insert a new
followup to 1003? 1001 and 1002 can stay unchanged. 1003 - 1005 need to
be incremented, and the new posting becomes 1003. The root stays at 2000.
This scheme satisfies both of the final constraints because arbitrary
insertion is accommodated and because any number of levels of followup is
allowed as well. At least, any number of followups that fit within the
range of 1000 numbers. Nested followups share the range of numbers with
any other followups.
But how does this method affect performance? Since the renumbering is
localized to just the region of numbers assigned to a single root, this
minimizes the impact. Since just the followups between the new posting and
the root must be visited, this reduces the impact further.
All ancestors of a new posting must be visited, to update their count of
descendents. We do this by moving back from the parent to the root. All
of the postings between the root and the new posting must have their
sequence numbers incremented. We do this by moving forward from the root
to the parent.
Unfortunately these two movements oppose each other. But, each requires
its specific direction of motion. The chain of ancestors flows bottom up
in the hierarchy, that is, from leaf to root. We learn the parent of a
posting from the posting's database record. This means traveling from
parent to grandparent etc. as we move back to the root via index1
(sequence number). Not all of the postings encountered are necessarily
in the lineage of the new posting.
For updating the sequence numbers, we move forward from the root because
otherwise we would be assigning duplicate numbers temporarily, which is
enough time to do damage. For example, say we have root 2000 and followups
1002 and 1001 under it. We want to insert a followup to 1001. Note
that it doesn't matter whether 1001 and 1002 are both followups to 2000,
or 1001 is a followup to 1002. We want the end result of the insertion
to be the sequence 2000, 1003, 1002, 1001 with the new posting being 1001.
If we start the renumbering with the old 1001 and move back towards the
root, when we make 1001 into 1002 we now temporarily have two 1002's.
This throws off the index and plays havoc. Skipping back from this finds
2000, not the original 1002. So, we must start from the root and move
forward, making 1002 into 1003, then 1001 into 1002. Then we can safely
create a new sequence number of 1001 for the new posting.
One final note on performance considerations is the following. The current
maximum sequence number is stored in the super root as its sequence number.
This means that we have to retrieve that sequence number before inserting
a main posting, and update that number with the new value. But we have to
access the super root's database record anyway when we create a posting,
because we need to update its number of descendents, which generates the
message number for the new posting.
By using a 10-digit number for sequencing, and allowing 1000 followups per
main posting, we are allowing for 10,000,000 main postings. We can change
these values, but doing so means that existing postings would be obsolete.
</MvCOMMENT>
<MvASSIGN NAME = "max_seqnum_increment" VALUE = "{'0000001000'}">
<MvASSIGN NAME = "seqnum_size" VALUE = "{'10'}">
<MvCOMMENT> Increment for creating self and parent numbers: </MvCOMMENT>
<MvASSIGN NAME = "big_bump" VALUE = "{'1000000'}">
<MvASSIGN NAME = "db_just_created" VALUE = "{'0'}">
<MvASSIGN NAME = "textwidth" VALUE = "{'60'}">
<MvASSIGN NAME = "urlwidth" VALUE = "{'80'}">
<MvASSIGN NAME = "admin_banner_ad" VALUE = "{''
}">
<MvASSIGN NAME = "banner_ad" VALUE = "{''
}">
<MvASSIGN NAME = "newline" VALUE = "{asciichar(10)}">
<MvASSIGN NAME = "carriage" VALUE = "{asciichar(13)}">
<MvASSIGN NAME = "crlf" VALUE = "{carriage $ newline}">
<MvASSIGN NAME = "amp" VALUE = "{asciichar(38)}">
<MvASSIGN NAME = "quote" VALUE = "{asciichar(39)}">
<MvASSIGN NAME = "longquote" VALUE = "{'''}">
<MvASSIGN NAME = "period" VALUE = "{asciichar(46)}">
<MvASSIGN NAME = "slash" VALUE = "{asciichar(47)}">
<MvASSIGN NAME = "less" VALUE = "{asciichar(60)}">
<MvASSIGN NAME = "greater" VALUE = "{asciichar(62)}">
<MvASSIGN NAME = "qmark" VALUE = "{asciichar(63)}">
<MvASSIGN NAME = "atsign" VALUE = "{asciichar(64)}">
<MvASSIGN NAME = "backslash" VALUE = "{asciichar(92)}">
<MvASSIGN NAME = "newlines" VALUE = "{crlf $ crlf}">
<MvASSIGN NAME = "longless" VALUE = "{'<'}">
<MvASSIGN NAME = "longgreater" VALUE = "{'>'}">
<MvASSIGN NAME = "dquote" VALUE = "{'\"'}">
<MvASSIGN NAME = "longdquote" VALUE = "{'"'}">
<MvASSIGN NAME = "bullet" VALUE = "{asciichar(149)}">
<MvASSIGN NAME = "nbsp" VALUE = "{asciichar(174)}">
<MvASSIGN NAME = "longnbsp" VALUE = "{' '}">
<MvASSIGN NAME = "indent" VALUE = "{' '}">
<MvASSIGN NAME = "delim" VALUE = "{'!!##!!'}">
<MvASSIGN NAME = "yuml" VALUE = "{asciichar(255)}">
<MvCOMMENT>###########################################################</MvCOMMENT>
<MvCOMMENT>###########################################################</MvCOMMENT>
<MvCOMMENT> MAIN PROCESSING </MvCOMMENT>
<MvEVAL EXPR = Initialization()>
<MvIF EXPR = "{parm_func EQ 'showmain' OR parm_func EQ ''}">
<MvEVAL EXPR = ShowMain()>
<MvEXIT>
<MvELSE>
<MvIF EXPR = "{parm_func EQ 'showmsg'}">
<MvEVAL EXPR = ShowMessage()>
<MvEXIT>
<MvELSE>
<MvIF EXPR = "{parm_func EQ 'showpostform'}">
<MvEVAL EXPR = ShowPostForm()>
<MvEXIT>
<MvELSE>
<MvIF EXPR = "{parm_func EQ 'showsearch'}">
<MvEVAL EXPR = ShowSearchForm()>
<MvEXIT>
<MvELSE>
<MvIF EXPR = "{parm_func EQ 'post'}">
<MvEVAL EXPR = PostControl()>
<MvEXIT>
<MvELSE>
<MvIF EXPR = "{parm_func EQ 'postpreview'}">
<MvEVAL EXPR = ShowPostPreview()>
<MvEXIT>
<MvELSE>
<MvIF EXPR = "{parm_func EQ 'followuppreview'}">
<MvEVAL EXPR = ShowFollowupPreview()>
<MvEXIT>
<MvELSE>
<MvIF EXPR = "{parm_func EQ 'showfaq'}">
<MvEVAL EXPR = ShowFAQ()>
<MvEXIT>
<MvELSE>
<MvIF EXPR = "{parm_func EQ 'admin'}">
<MvEVAL EXPR = Admin()>
<MvEXIT>
</MvIF>
</MvIF>
</MvIF>
</MvIF>
</MvIF>
</MvIF>
</MvIF>
</MvIF>
</MvIF>
<MvEXIT>
<MvCOMMENT> END OF MAIN PROCESSING </MvCOMMENT>
<MvFUNCTION NAME = "Initialization">
<MvIF EXPR = "{nargs GT 1 AND arg2 EQ 'admin'}">
<MvASSIGN NAME = "parm_func" VALUE="{'admin'}">
<MvELSE>
<MvEVAL EXPR = GetFormInput()>
</MvIF>
<MvEVAL EXPR = FindMboardName()>
<MvIF EXPR = "{NOT fexists(dirprefix)}">
<MvASSIGN NAME = "x" VALUE = "{fmkdir(dirprefix)}">
</MvIF>
<MvASSIGN NAME = "msgfiledir" VALUE = "{dirprefix $ '/' $ mesgdir}">
<MvIF EXPR = "{NOT fexists(msgfiledir)}">
<MvASSIGN NAME = "x" VALUE = "{fmkdir(msgfiledir)}">
</MvIF>
<MvIF EXPR = "{NOT fexists(database)}">
<MvEVAL EXPR = CreateDatabase()>
</MvIF>
<MvASSIGN NAME = "passwdfile" VALUE = "{dirprefix $ '/' $ passwd_file}">
<MvIF EXPR = "{NOT fexists(passwdfile)}">
<MvIF EXPR = "{action EQ 'provide_identity'}"> <MvCOMMENT> Done til input errors fixed </MvCOMMENT>
<MvEVAL EXPR = ProvideIdentity("ShowProvideIdentity")>
<MvELSE>
<MvIF EXPR = "{parm_func EQ ''}"> <MvCOMMENT> 1st time in </MvCOMMENT>
<MvEVAL EXPR = ShowWelcome()>
<MvELSE>
<MvEVAL EXPR = ShowProvideIdentity()>
</MvIF>
</MvIF>
</MvIF>
<MvASSIGN NAME = "configfile" VALUE = "{dirprefix $ '/' $ config_file}">
<MvIF EXPR = "{NOT fexists(configfile)}">
<MvEVAL EXPR = SetupLinks()> <MvCOMMENT> No title yet, but don't care. </MvCOMMENT>
<MvEVAL EXPR = AssignDefaultSettings()>
<MvEVAL EXPR = ExportSettings()>
<MvIF EXPR = "{p_user NE ''}"> <MvCOMMENT> If we just came from ShowProvideIdentity </MvCOMMENT>
<MvEVAL EXPR = ChooseAdminFunction(p_user,p_pass1)>
<MvELSE>
<MvEVAL EXPR = ChooseAdminFunction(parm_user,parm_pass)>
</MvIF>
<MvEXIT>
</MvIF>
<MvASSIGN NAME = "unwelcomelist" VALUE = "{dirprefix $ '/' $ unwelcome_list}">
<MvASSIGN NAME = "unwelcomelist2" VALUE = "{dirprefix $ '/' $ unwelcome_list2}">
<MvEVAL EXPR = GetSettings()>
<MvEVAL EXPR = DateAndTime()>
<MvEVAL EXPR = SetupLinks()>
</MvFUNCTION>
<MvFUNCTION NAME = "GetFormInput">
<MvASSIGN NAME = "lenny" VALUE = "{len(QUERY_STRING)}">
<MvASSIGN NAME = "curr_pos" VALUE = "{1}">
<MvWHILE EXPR = "{curr_pos LE lenny}">
<MvASSIGN NAME = "a1" VALUE = "{'=' CIN substring(QUERY_STRING,curr_pos,lenny-curr_pos+1)}">
<MvIF EXPR = "{a1 GT 0}">
<MvASSIGN NAME = "a2" VALUE = "{'+' CIN substring(QUERY_STRING,curr_pos+a1,lenny-curr_pos-a1+1)}">
<MvIF EXPR = "{a2 EQ 0}">
<MvASSIGN NAME = "a2" VALUE = "{lenny-curr_pos-a1+2}">
</MvIF>
<MvASSIGN NAME = "x" VALUE = "{substring(QUERY_STRING,curr_pos,a1-1)}">
<MvASSIGN NAME = "&[x]"
VALUE = "{substring(QUERY_STRING,curr_pos+a1,a2-1)}">
</MvIF>
<MvASSIGN NAME = "curr_pos" VALUE = "{curr_pos + a1 + a2}">
</MvWHILE>
</MvFUNCTION>
<MvFUNCTION NAME = "FindMboardName">
<MvCOMMENT>
The name of the msgboard is what immediately precedes ".mv" in the
document URL, but parsing depends on whether the URL is an NSAPI or CGI URL.
The user must give each message board Miva doc a unique name, e.g. msgboard1.mv,
msgboard2.mv, etc. Each message board will have a separate subdirectory
under mivadata. The subdirectory will be named with the unique message board
name, e.g. msgboard1. The path to the database files, etc. will be based on the
message board name, e.g. mivadata/msgboard1/msgboard.dbf.
If CGI, URL looks like either
.../cgi-bin/miva?xxx.mv+
or
.../cgi-bin/miva?zzz/xxx.mv+
where zzz is the path within the Documents directory (so could have >1 slash).
If NSAPI, URL looks like either
...xxx.mv?
or
...zzz/xxx.mv?
where zzz is the path within the Documents directory (so could have >1 slash).
</MvCOMMENT>
<MvASSIGN NAME = "a1" VALUE = "{'.mv' CIN documenturl}">
<MvASSIGN NAME = "curr_pos" VALUE = "{a1 - 1}">
<MvIF EXPR = "{substring(documenturl,curr_pos+4,1) EQ qmark}">
<MvWHILE EXPR = "{substring(documenturl,curr_pos,1) NE slash}">
<MvASSIGN NAME = "curr_pos" VALUE = "{curr_pos - 1}">
</MvWHILE>
<MvELSE>
<MvWHILE EXPR = "{(substring(documenturl,curr_pos,1) NE slash)
AND (substring(documenturl,curr_pos,1) NE qmark)}">
<MvASSIGN NAME = "curr_pos" VALUE = "{curr_pos - 1}">
</MvWHILE>
</MvIF>
<MvASSIGN NAME = "dirprefix" VALUE = "{substring(documenturl,curr_pos+1,a1-curr_pos-1)}">
<MvASSIGN NAME = "database" VALUE = "{'&[dirprefix];/msgboard.dbf'}"><MvCOMMENT> path name </MvCOMMENT>
<MvASSIGN NAME = "index_file1" VALUE = "{'&[dirprefix];/msgboard1.mvx'}"><MvCOMMENT> path name </MvCOMMENT>
<MvASSIGN NAME = "index_file2" VALUE = "{'&[dirprefix];/msgboard2.mvx'}"><MvCOMMENT> path name </MvCOMMENT>
<MvASSIGN NAME = "index_file3" VALUE = "{'&[dirprefix];/msgboard3.mvx'}"><MvCOMMENT> path name </MvCOMMENT>
<MvASSIGN NAME = "dbname" VALUE = "{'&[dirprefix];'}"><MvCOMMENT> short name </MvCOMMENT>
</MvFUNCTION>
<MvFUNCTION NAME = "SetupLinks">
<MvASSIGN NAME = "link_main_short"
VALUE = "{'[ <a href="&[documenturl];parm_func=showmain+parm_starting_root=1">&[title];</a> ]'}">
<MvASSIGN NAME = "link_main_short_dflt"
VALUE = "{'[ <a href="&[documenturl];parm_func=showmain+parm_starting_root=1">Message Board</a> ]'}">
<MvASSIGN NAME = "link_post_short"
VALUE = "{'[ <a href="&[documenturl];parm_func=showpostform">Post Message</a> ]'}">
<MvASSIGN NAME = "link_search_short"
VALUE = "{'[ <a href="&[documenturl];parm_func=showsearch+parm_starting_root=1">Search</a> ]'}">
<MvASSIGN NAME = "link_faq_short"
VALUE = "{'[ <a href="&[documenturl];parm_func=showfaq">FAQ</a> ]'}">
</MvFUNCTION>
<MvFUNCTION NAME = "GetSettings">
<MvCOMMENT>
Importing a single big field and parsing it into data fields takes
one second. Importing the fields individually takes several seconds.
</MvCOMMENT>
<MvIMPORT FILE = "&[configfile];" DELIMITER = "%%%%%%" FIELDS = "glob">
</MvIMPORT>
<MvASSIGN NAME = "curr_pos" VALUE = "{1}">
<MvEVAL EXPR = ExtractSetting("roots_to_view")>
<MvEVAL EXPR = ExtractSetting("entries_thread")>
<MvEVAL EXPR = ExtractSetting("entries_msgnum")>
<MvEVAL EXPR = ExtractSetting("entries_date")>
<MvEVAL EXPR = ExtractSetting("entries_author")>
<MvEVAL EXPR = ExtractSetting("time_zone")>