<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 = "{'&#039;'}">

<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 = "{'&lt;'}">

<MvASSIGN NAME = "longgreater"     VALUE = "{'&gt;'}">

<MvASSIGN NAME = "dquote"      VALUE = "{'\"'}">

<MvASSIGN NAME = "longdquote"      VALUE = "{'&quot;'}">

<MvASSIGN NAME = "bullet"      VALUE = "{asciichar(149)}">

<MvASSIGN NAME = "nbsp"            VALUE = "{asciichar(174)}">

<MvASSIGN NAME = "longnbsp"      VALUE = "{'&nbsp;'}">

<MvASSIGN NAME = "indent"      VALUE = "{'&nbsp;&nbsp;&nbsp;&nbsp;'}">

<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")>