/* dCGISession A class that mangages CGI operations when dBASE is used  to
               create Web Applets. The current version supports only CGI.

   New Methods (some inspired by Ken Mayer):
      errorMessage    -- streams out a message on a page that has already started to display
      streamTitle     -- stream out the title at the top of the page <h1>
      streamSubTitle  -- stream out the title at the top of the page <h3>
      streamDetail    -- streams out individual lines of text and/or html
      streamFormBegin -- steams out the <FORM> code with the cgi definition you specify for what to do
                         with the form when it's submitted
      streamFormEnd   -- streams out the end tag for a form
      streamText      -- streams out an HTML entryfield
      streamPassword  -- streams out an HTML entryfield with a password mask
      streamRadio     -- streams out an HTML radio button
      streamCheckbox  -- streams out an HTML checkbox
      streamReset     -- streams out an HTML "Reset" button
      streamSubmit    -- streams out an HTML "Submit" button
      streamTextArea  -- streams out an HTML Editor control
      streamSelectBegin -- streams out an HTML Combobox or Listbox (see details in documentation for this method)
      streamOption    -- stream out elements used in Select
      streamSelectEnd -- end tag for a select object
      streamHidden    -- special object used to pass values in standard Name/Value pairs to an application when a 
                         form is posted, which do not have any UI (i.e., the user cannot see/interact with them!)
      streamResponsiveHeader -- streams out responsive header including css and javascript for a responsive web page
      streamResponsiveSetup -- streams out css and javascript for a responsive web page
	  streamLogo      -- streams out logo (default is dBASE)
	  streamWebVars   -- streams out Web data passed to CGI program
      modSQLQuery     -- modifies Existing SQL string to add a restriction clause (required) and to localize the data table (optional)

   Revised Methods:
      streamBody -- streams out the BODY tag, but with some parameters not in the original ...
      sorryPage  -- modified to use the methods defined here, and passing colors for the title ...
      errorPage  -- modified to handle passing colors through for the title ...
	  
   Existing Methods:	  
      streamHeader -- streams out a header for CGI output
      LoadArrayFromFields -- Load field info from dataset
      loadDataModuleFromArray -- Load arrary info to update dataset
      PassDataThrough -- Passes through all received CGI data to the next form

   Usage:
      To use this class:

      set procedure to webclass.cc additive
      oCGI = new CGISession()

      // set default colors (this is pretty garish, and it's
      // just an example):
      oCGI.BackgroundColor := "black"
      oCGI.BackgroundImage := "myimage.gif"
      oCGI.TextColor       := "green"
      oCGI.LinkColor       := "red"
      oCGI.vLinkColor      := "lightblue"
      oCGI.aLinkColor      := "navy"
      oCGI.TitleBackColor  := "white"
      oCGI.TitleTextColor  := "black"
      // NOTE: these colors can also use the RGB
      // color format: "#000000" == black
      //               "#00FF00" == green
      //    etc. You need to look up the colors
      //    you want for that. I like using names,
      //    but not all colors have names ...

      // From there, use the class as you would any other ...
      // You could also subclass this class to create your
      // own, and set the colors there, or override any
      // of the classes shown here.

   NOTE: When building your executable, you will need to be
         sure to include the webclass.co in the executable. 

   Author:	   A.A.Katz, author
               v.1.0 11/27/1999
               v.1.1 01/12/2000 (supports Win 98 STDOUT)
               v.1.2 05/24/2001 Loop with timeout added to STDIN read
                                __asian__ build OEMFormat method added
               v.1.3 08/24/2001 loadArrayFromFields method revised
               v.1.4 09/04/2001 Chinese, Korean added to OEMFormat method #ifdef __asian
               v.1.5 01/31/2002 'AUTOINC' string comparisons standardized
			   v.3.0 08/28/2016 Updated for dBASE PLUS 11 

   Rights:	   You have the right to freely include this code in any compiled
   			   dBASE Web application. You do not have the right to reproduce
               or publish this source code without the explicit written
               permission of dBASE, LLC., Binghampton, NY
               (c) 1999-2000 dBASE Inc.
			   (c) 2012-2016 dBase LLC.
			   
   Platform:   This code works only with Visual dBASE 7.5 or later.
   			   If this code is used, you do not need to use VdB_CGI.exe
               as required with earlier versions. Your Visual dBASE
               applications may be called directly by the Web Server.
*/


Class CGISession of assocArray
   ////// Constructor code

   this.nTimeout = 45             // STDIN read timeout in seconds #####

   // create default color and background image settings here
   // that will affect the whole HTML document and any document
   // using this instance of the class:
   
   // NOTE: colors must be in either text format (i.e., "BLUE" )
   //       or in hexidecimal (RGB) with the "#" in front of
   //       them ...

   this.defaultLogo = "images/dBaseLogo.png" // default logo image for streamed and generated static html pages
   
   // These are used by the streamBody() method if
   // no values are passed:
   this.BackgroundImage = "" // image that is displayed over the whole document
   this.BackgroundColor = "#F1FAFE" // default background color of an HTML document (Web Wizard base color)
   this.TextColor       = "BLACK" // default color of text on a page
   this.LinkColor       = "BLUE" // default link color (default is blue)
   this.VLinkColor      = "" // default Visited link color (one that the user has clicked on -- default is purple)
   this.ALinkColor      = "" // default color of link that is 'active' 

   // These are used by the streamTitle() method if 
   // no values are passed:
   this.TitleBackColor  = "" 
   this.TitleTextColor  = "" 

   ////// Set dBASE environment commands. Web apps are non-GUI

   SET CENTURY ON
   SET TALK OFF
   

   ////// Method:		Connect //////////////////////////////////////////////////
   ////// Purpose		Connects by StdIn and StdOut to Web server ///////////////
   //////            reads in data, and then parses out name/value pairs //////
   //////				and creates a new element of this array for each. ////////

   Function Connect

      // Set default WebMaster EMail address

      this["WebMasterEMail"] = ""

      // Read incoming data into this array as name/value pairs
      // Ex: this["City] = "Binghampton", where "City" is the name
      // and "Binghampton" is the value

      this.loadArrayFromCGI()

      ////// Open  output stream back to Web Server
      Try

         this.fOut = new file()
         this.fOut.Open("StdOut", "RA")  // open the StdOut output stream

      Catch (exception e)

         Quit 							// Might as well quit, can't send response

      endTry


   ////// Method:  LoadArrayFromGGI //////////////////////////////////////
   ////// Purpose: Loads existing assoc array with data from  ////////////
   //////  	       Web Server from a previous page////////////////////////
   ////// Params:  None //////////////////////////////////////////////////

   /* Note: This code connects to the Web Server and captures the incoming
   			data sent when the HTML form was submitted. This Associative
            Array, fortunately, mimics the Web's Name/Value pairs.
            Example: this["City"] = "Binghampton", where "City" is the name and
           "Binghampton" is the value.
   */

   Function LoadArrayFromCGI

      // First, look in environment to see if the page was submitted via
      // Post or Get

      cMethod = upper(getEnv("REQUEST_METHOD"))

      if cMethod = 'POST'    // if the submit method was "Post" (Recommended!!!)
                             // get length of incoming data string
         nLen = val(getEnv("CONTENT_LENGTH"))
         nCnt = 0            // Initialize bytes read counter #####

         Try

            fIn  = new file()        // Create new instance of File Object
            fIn.Open("StdIn", "RA")  // open the StdIn input stream
            cEnv = ""                // Initialize read buffer
            cStart = time()          // Save start time
            // Loop until all bytes are read or timeout occurs
            do while nCnt < nLen
               cEnv += fIn.Read(nLen - nCnt)  // Read additional bytes
               nCnt := len ( cEnv )  // Update byte counter
               // Compute seconds elapsed, compensated for date rollover
               if this.nTimeout < IIf(elapsed(time(), cStart) < 0,;
                                      elapsed(time(), cStart) + 86400,;
                                      elapsed(time(), cStart))
                  exit               // Exit if timeout value exceeded
               endIf
            endDo

         Catch (exception e)

            Quit        				// Might as well quit, can't open connection

         endTry

         // Note: StdIn and StdOut cannot be close()'ed.

      else

         cEnv = getEnv("QUERY_STRING") // Get data from environment

      endif

      /* Note: Name/Value pairs are sent in a form something like this:
               City=Binghampton&State=NY&Country=USA  with "&" as delimeter
               between named pairs.

         Note: Punctuation and spaces are sent using special characters.
               Furthermore, the Internet uses the International character
               standard: ANSI while dBASE uses its own internal character
               standard: OEM. The OEMFormat() method handles all
               conversions to dBASE-usable character strings automatically.
      */

      ////// Break up CGI input string into
      ////// Name/Value pairs

      try

         do while at("=",cEnv) > 0   // Do while there are still pairs

            cName  = ''             // init name/value vars
            cValue = ''

            If at("&",cEnv) > 0      // If there is more than one pair
			
               // Grab the pair before first "&'
               cEnvPair = this.OEMFormat(substr(cEnv,1,at("&",cEnv)-1))

               // Strip this pair off of cEnv
               // in preparation for next loop
               cEnv = substr(cEnv,at("&",cEnv)+1)

            else // This must be the last pair

               cEnvPair = this.OEMFormat(cEnv) // grab remainder
               cEnv = ''             // Nothing left to parse

            endif

            ////// separate name and value
            cName = substr(cEnvPair,1,at('=',cEnvPair)-1)+""
            cValue = substr(cEnvPair, at('=',cEnvPair)+1)+""

            ////// if name/value pair exists, append into this array
            if len(trim(cName)) >0 //

               this[cName] = cValue  // update or add to array

            endif

            ////// Add in HTTP Variables
            this["QUERY_STRING"] = getEnv("QUERY_STRING") // Get data from environment
            this["DOCUMENT_ROOT"] = getEnv("DOCUMENT_ROOT") // 	The root directory of your server
            this["HTTP_COOKIE"] = getEnv("HTTP_COOKIE") // 	The visitor's cookie, if one is set
            this["HTTP_HOST"] = getEnv("HTTP_HOST") // 	The hostname of your server
            this["HTTP_REFERER"] = getEnv("HTTP_REFERER") // 	The URL of the page that called your script
            this["HTTP_USER_AGENT"] = getEnv("HTTP_USER_AGENT") // 	The browser type of the visitor
            this["HTTPS"] = getEnv("HTTPS") // 	"on" if the script is being called through a secure server
            this["PATH"] = getEnv("PATH") // 	The system path your server is running under
            this["REMOTE_ADDR"] = getEnv("REMOTE_ADDR") // 	The IP address of the visitor
            this["REMOTE_HOST"] = getEnv("REMOTE_HOST") // 	The hostname of the visitor (if your server has reverse-name-lookups on; otherwise this is the IP address again)
            this["REMOTE_PORT"] = getEnv("REMOTE_PORT") // 	The port the visitor is connected to on the web server
            this["REMOTE_USER"] = getEnv("REMOTE_USER") // 	The visitor's username (for .htaccess-protected pages)
            this["REQUEST_URI"] = getEnv("REQUEST_URI") // 	The interpreted pathname of the requested document or CGI (relative to the document root)
            this["SCRIPT_FILENAME"] = getEnv("SCRIPT_FILENAME") // 	The full pathname of the current CGI
            this["SCRIPT_NAME"] = getEnv("SCRIPT_NAME") // 	The interpreted pathname of the current CGI (relative to the document root)
            this["SERVER_ADMIN"] = getEnv("SERVER_ADMIN") // 	The email address for your server's webmaster
            this["SERVER_NAME"] = getEnv("SERVER_NAME") // 	Your server's fully qualified domain name (e.g. www.cgi101.com)
            this["SERVER_PORT"] = getEnv("SERVER_PORT") // 	The port number your server is listening on
            this["SERVER_SOFTWARE"] = getEnv("SERVER_SOFTWARE") // 	The server software you're using (such as Apache 1.3)

         enddo

      catch (exception e)

         this.errorPage(e)

      endtry

      return true

   ////// Method:  streamWebVars //////////////////////////////////////
   ////// Purpose: displays the web variables sent to CGI program ////
   ////// Params:  None //////////////////////////////////////////////////

   /* Note: This code connects to the Web Server and captures the incoming
   			data sent when the HTML form was submitted. This Associative
            Array, fortunately, mimics the Web's Name/Value pairs.
            Example: this["City"] = "Binghampton", where "City" is the name and
           "Binghampton" is the value.
   */

   Function streamWebVars

     this.fOut.Puts('<p>')
	  cKey = this.firstKey // Get first key in AssocArray of Web Variables passed in
         for nElements = 1 to this.count()
             this.fOut.Puts('Key :: '+cKey+' = '+this[ cKey ]+'<br>')
            cKey := this.nextKey( cKey ) // Get next key value 
         endfor
     oCGI.fOut.Puts('</p>')
	 
   ////// Method 	OEMFormat /////////////////////////////////////////////////
   ////// Purpose 	Transforms CGI escape chars to OEM/////////////////////////
   ////// Param: 	cStr 	Character strings sent by Web browser ///////////////
   //////			        in ANSI format ////////////////////////////////////////////

   /* Note: Strings come in over the internet in ANSI format and must be
            converted to dBASE OEM Format before use. Punctuation is
            sent in %nn hexadecimal format and spaces to plus signs (+).
            This method does all the conversion necessary to convert from
            ANSI CGI to dBASE OEM.
            Example: Alan's New Class, in ANSI CGI is
                     Alan%27s+New+Class
   */

   #ifdef __asian__

   Function OEMFormat(cStr) Protect // For internal use only
      // this version [May.2000] supports Shift-JIS double-byte Japanese

      // remove "Pluses" used as space keepers in CGI
      do while at("+",cStr) > 0
         cStr = substr(cStr,1,at("+",cStr)-1) + " "+;
                substr(cStr,at("+",cStr)+1)
      enddo

      // add in "CRs" sent as escape characters
      do while at("%0D%0A",cStr) > 0
         cStr = substr(cStr,1,at("%0D%0A",cStr)-1) +chr(13)+;
                substr(cStr,at("%0D%0A",cStr)+3)
      enddo

      // convert escape characters back to oem chars
      // non-ascii and Asian chars likely encoded as '%nn' per each byte
      // loop as long as '%' exists in string

      nLeft = 1
      do while at("%",substr(cStr,nLeft) ) > 0
         nPos = at("%",substr(cStr,nLeft))+nLeft-1

         nLeft = nPos+1
         if nPos <= len(cStr)-2

           jumpAhead = 3
           // convertincoming encoding to character
           char1 = chr( htoi(substr(cStr,nPos+1,2)) )
           validCharacter = true

           // is char1 a lead byte of a double-byte character?
           // Japanese Shift-JIS lead bytes are only in the following range

           // convert nn to decimal
           chrCode = htoi(substr(cStr,nPos+1,2))

           isLeadByte = false
           if '932' $ _app.charset // Japanese Shift-JIS
                                   // lead-byte range (non-Contiguous)
                                   //  81h - 9Fh and then E0h - FCh
              isLeadByte = ( chrCode >= 129 .and. chrCode <= 159 ) .or.;
                           ( chrCode >= 224 .and. chrCode <= 239 )

           elseif '936' $ _app.charset //  Simplified Chinese
                                  //  lead byte range A1h - FEh
              isLeadByte = ( chrCode >= 161 .and. chrCode <= 254 )

           elseif '949' $ _app.charset // Korean
                                  // leadByte range 81h - FEh
              isLeadByte = ( chrCode >= 129 .and. chrCode <= 254 )

           elseif '950' $ _app.charset // Traditional Chinese
                                  // lead byte range A1h - FEh
              isLeadByte = ( chrCode >= 161 .and. chrCode <= 254 )

           endif

           if isLeadByte
              // we have a lead byte
              // must grab the trail byte, and then convert to character

              // trail byte is not _always_ %nn encoded
              //   as CGI passes them thru as is,
              //   when they happen to match a lower-case ASCII codePoint
              if substr( cStr, nPos+3, 1 ) = "%"
                 // trail byte is %nn encoded
                 char1 = chr( htoi( substr(cStr,nPos+1,2) + substr( cStr, nPos+4, 2 )))
                 jumpAhead = 6
              else
                 // trail byte is plain text
                 char1 = chr( htoi( substr(cStr,nPos+1,2) + itoh( asc( substr( cStr, nPos+3, 1 ))) ))
                 jumpAhead = 4
              endif
              validCharacter = iskanji( char1 )
           endif

           // throw away character if invalid
           if validCharacter
              cStr = left(cStr,nPos-1)+ char1 +;
                  substr(cStr,nPos+jumpAhead)
           else
              cStr = left(cStr,nPos-1)+ substr(cStr,nPos+3)
           endif
         endif
      enddo

      return cStr

   #else

   Function OEMFormat(cStr) Protect // For internal use only

      // remove "Pluses" used as space keepers in CGI
      do while at("+",cStr) > 0
         cStr = substr(cStr,1,at("+",cStr)-1) + " "+;
                substr(cStr,at("+",cStr)+1)
      enddo

      // add in "CRs" sent as escape characters
      do while at("%0D%0A",cStr) > 0
         cStr = substr(cStr,1,at("%0D%0A",cStr)-1) +chr(13)+;
                substr(cStr,at("%0D%0A",cStr)+3)
      enddo

      // convert escape characters back to oem chars
      nLeft = 1
      do while at("%",substr(cStr,nLeft) ) > 0
         nPos = at("%",substr(cStr,nLeft))+nLeft-1
         nLeft = nPos+1
         if nPos <= len(cStr)-2
            cStr = left(cStr,nPos-1)+ chr(htoi(substr(cStr,nPos+1,2)))+;
            substr(cStr,nPos+3)
         endif
      enddo

      return cStr

   #endif


   ////// Method: 	SetWebMasterAddress //////////////////////////////////
   ////// Purpose:	Sets mailto: address used in Error page //////////////
   ////// Param:	cWebMasterEMail (ex: webmaster@my.server) ////////////

   Function SetWebMasterAddress(cMailAddress)
      if not empty(cMailAddress)
         this["WebMasterEMail"] = cMailAddress
      endif
      return true


   /////////////////////// Output Methods //////////////////////////////////

   // -----------------------------------------------------------------
   // Streaming of HTML Documents -- I am modifying some of the 
   // original code in WebClass.cc so I have to copy the code
   // itself to here. In addition, I am giving a bit more flexibility
   // in the appearance and such ... this allows you the ability
   // to customize a bit easier ...
   // -----------------------------------------------------------------

   // -----------------------------------------------------------------
   // streamTitle
   // -----------------------------------------------------------------
   Function streamTitle( cTitle, cBackColor, cTextColor )
      /*
          streamTitle is used to stream out a title that works
          like the one in the primary webclass for the sorryPage
          and errorPage methods ... it's simply done with
          the use of a table and setting the background for
          the cell in the table. However, this allows you to
          pass a parameter for the two colors (if you wish),
          and if you wish, you can set a default color here
      */
      // if no value passed, use default ...
      cBackColor = iif( empty( cBackColor ), this.TitleBackColor, cBackColor )
      cTextColor = iif( empty( cTextColor ), this.TitleTextColor, cTextColor )

      // Title:
      this.streamDetail([<div class="container">])
      this.streamDetail([<div class="row">])
      if not empty( cBackColor )
	     sColor = '<span style="background-color:'+cBackColor+';'
		 if not empty ( cTextColor)
		    sColor += 'color:'+cTextColor
		 endif
		 sColor += '">'
         this.streamDetail([<div align="center">]+sColor+[<h1>]+cTitle+[</h1></span></div>])
	  else
         this.streamDetail([<div align="center"><h1>]+cTitle+[</h1></div>])
      endif
      this.streamDetail([</div>])
      this.streamDetail([</div>])

      // Blank Line:
      this.streamDetail( [<!-- End of streamTitle -->] )
   // end of method: streamTitle      

   // -----------------------------------------------------------------
   // streamSubTitle
   // -----------------------------------------------------------------
   Function streamSubTitle( cTitle, cBackColor, cTextColor )
      /*
          streamSubTitle is used to stream out a 2nd title that works
          like streamTitle.
      */
      // if no value passed, use default ...
      cBackColor = iif( empty( cBackColor ), this.TitleBackColor, cBackColor )
      cTextColor = iif( empty( cTextColor ), this.TitleTextColor, cTextColor )

      // SubTitle:
      this.streamDetail([<div class="container">])
      this.streamDetail([<div class="row">])
      if not empty( cBackColor )
	     sColor = '<span style="background-color:'+cBackColor+';'
		 if not empty ( cTextColor)
		    sColor += 'color:'+cTextColor
		 endif
		 sColor += '">'
         this.streamDetail([<div align="center">]+sColor+[<h3>]+cTitle+[</h3></span></div>])
	  else
         this.streamDetail([<div align="center"><h3>]+cTitle+[</h3></div>])
      endif
      this.streamDetail([</div>])
      this.streamDetail([</div>])

      // Blank Line:
      this.streamDetail( [<!-- End of streamTitle -->] )
   // end of method: streamTitle      

   // -----------------------------------------------------------------
   // streamDetail - streams out HTML line of code
   // -----------------------------------------------------------------
   Function streamDetail( cString )

      // cString is the string you want streamed out -- it can
      // contain HTML ...
      // NOTE: This should be called after the streamHeader() and streamBody() methods are called ...
		
      this.fOut.Puts( ""+cString )

   // end of method: streamDetail

   // -----------------------------------------------------------------
   // Revised methods of the CGISession class in Webclass.cc
   // -----------------------------------------------------------------

   // -----------------------------------------------------------------
   // streamBody - streams out the body tag and adds responsive code
   // -----------------------------------------------------------------
   Function streamBody(cBackImage, cBackColor, cTextColor, cLinkColor, cVLinkColor, cALinkColor)

      /*
         This streams out the HTML body tag used at the top
         of an HTML document ... however, note that you can 
         pass the colors that you want to display in your
         HTML document ... all settings for a page get set here.
         Note that the colors must use either standard named
         colors (e.g., "BLUE", "LIGHTBLUE", etc.) or standard
         hexidecimal colors with the "#" in front of them (e.g.,
         "#999999" which is white) 

         If the color parameters are left out, the default is a
         white page with black text, etc.

         Parameters:
            cBackImage -- background image used on the form
            cBackColor -- background color (under the image, if any)
            cTextColor -- text color -- this is important for text
                          on top of a background color ...
            cLinkColor -- default color of links
            cVLinkColor -- Viewed links (once that have been clicked on
                           by a user)
            cAlinkColor -- Active link (if the cursor is over the link
                           and the mouse button is pressed ...)
      */

      // note that you can set defaults in the constructor code:
      cBackImage  = iif(empty(cBackImage),  this.BackgroundImage, cBackImage )
      cBackColor  = iif(empty(cBackColor),  this.BackgroundColor, cBackColor )
      cTextColor  = iif(empty(cTextColor),  this.TextColor,       cTextColor )
      cLinkColor  = iif(empty(cLinkColor),  this.LinkColor,       cLinkColor )
      cVLinkColor = iif(empty(cVLinkColor), this.VLinkColor,      cVLinkColor )
      cALinkColor = iif(empty(cALinkColor), this.ALinkColor,      cALinkColor )

      cOut = ''
      if not empty( cBackImage )
         // Strip out path to extract filename
         if at('\',cBackImage) > 0
            cBackImage = substr(cBackImage,rat('\',cBackImage)+1)
         endif
         // Strip out path alias to extract filename
         if at(':',cBackImage) > 0
            cBackImage = substr(cBackImage,rat('\',cBackImage)+1)
         endif	     
         cOut += [ background: url(]+cBackImage+[);]
      endif
      if not empty( cBackColor )
         cOut += [ background-color:]+this.HTMLColor(cBackColor)+[;]
      endif
      if not empty( cTextColor )
         cOut += [ color:]+cTextColor+[;]
      endif
      if not empty( cLinkColor )
         cOut += [ a:link: {color:]+cTextColor+[;}]
      endif
      if not empty( cVLinkColor )
         cOut += [ a:visited {color:]+cVLinkColor+[;}]
      endif
      if not empty( cALinkColor )
         cOut += [ a:active {color:]+cALinkColor+[;}]
      endif
	  
	  if not empty ( cOut)
	     cBodyOut = [<body style="]+cOut+[">]
	  else 
	     cBodyOut = [<body>]
	  endif
	  
     this.streamDetail( cBodyOut )
	  this.streamDetail([<!-- End of streamBody -->])
	  this.streamResponsiveSetup()
	  
   // end of method: streamBody

   // -----------------------------------------------------------------
   // streamFormBegin - streams out the form tag
   // -----------------------------------------------------------------
   Function streamFormBegin( cCGI )
       /*
          The form tag needs to know what to do when
          the user submits it, hence the "cCGI" parameter ... 
          if this is left out, the form will not actually
          do anything if you click the submit button ... it will
          work, but ...

          Your CGI/Action ought to be something like:
             "/cgi-bin/myprog.exe" 
       */
     cOut = [<form method="POST" ]
     if pCount() => 1
        cOut += [action="]+cCGI+["]
     endif
     cOut += [>]

     this.streamDetail( cOut )
	  this.streamDetail([<!-- End of streamFormBegin -->])
   // end of method: streamFormBegin

   // -----------------------------------------------------------------
   // streamFormEnd - streams out the end form tag
   // -----------------------------------------------------------------
   Function streamFormEnd
      this.streamDetail( "</form>" )
      this.streamDetail( [<!-- End of streamFormEnd -->] )
   // end of method: streamFormEnd

   // -----------------------------------------------------------------
   // streamText - streams out Form controls:
   // -----------------------------------------------------------------
   Function streamText( cName, nSize, cValue, nMaxLength )
      /*
         The 'text' control for HTML forms is
         the same as an entryfield ...
         Parameters:
           cName is the name -- if you want this to match a field,
                make sure you use *exact* case every time you 
                reference this, as the case is vital to the way 
                the web classes work (they are subclassed from 
                an Associative Array, and it is case sensitive).
           nSize is the width of the entryfield on the form --
                the size is character based ...
           cValue is the value you want to display -- could
                be the contents of the field
           nMaxLength is optional, and is the maximum amount
                of data you want to allow to be entered.
      */
      cOut = [<input type="TEXT" name="]+cName+["]
      if pCount() > 1
         cOut+= [ size=]+nSize
      endif
      if pCount() > 2
         cOut+= [ value="]+cValue+["]
      endif
      if pCount() > 3
         cOut+= [ maxlength=]+nMaxLength
      endif
      cOut+= [>]
      this.streamDetail( cOut )
      this.streamDetail( [<!-- End of streamText -->] )
   // end of method: streamText

   // -----------------------------------------------------------------
   // streamPassword - streams out password form controls
   // -----------------------------------------------------------------
   Function streamPassword( cName, nSize, cValue, nMaxLength )
      /*
          the password control in HTML is exactly the same
          as the text control, except that a password mask
          is displayed for each character entered, rather
          than displaying the character.

          The parameters for this control are exactly the
          same as the text control -- see comments for
          this above.
      */
      cOut = [<input type="PASSWORD" name="]+cName+["]
      if pCount() > 1
         cOut+= [ size=]+nSize
      endif
      if pCount() > 2
         cOut+= [ value="]+cValue+["]
      endif
      if pCount() > 3
         cOut+= [ maxlength=]+nMaxLength
      endif
      cOut+= [>]
      this.streamDetail( cOut )
      this.streamDetail( [<!-- End of streamPassword -->] )
   // end of method: streamPassword

   // -----------------------------------------------------------------
   // streamRadio - streams out radio button form controls
   // -----------------------------------------------------------------
   Function streamRadio( cName, cValue, cText, lChecked )
      /*
         radio controls in HTML use multiple controls
         just like in dBASE. In HTML they are grouped
         by the NAME property (cName parameter)
         The VALUE is returned for whichever radiobutton
         was checked when the HTML form was submitted ...

         Parameters:
             cName --    as text control
             cValue --   value returned for radiobutton selected
             cText --    text that appears to the right of the 
                         radiobutton
             lChecked -- is this radiobutton defaulting to 
                         a 'selected' state?
      */
      cOut = [<input type="RADIO" name="]+cName+["]
      cOut+= [ value="]+cValue+["]
      if pCount() > 3 and lChecked 
         cOut+= [ checked]
      endif
      cOut+= [>]
      if pCount() > 2
         cOut+= cText
      endif

      this.streamDetail( cOut )
      this.streamDetail( [<!-- End of streamRadio -->] )
   // end of method: streamRadio

   // -----------------------------------------------------------------
   // streamCheckbox - streams out checkbox form controls
   // -----------------------------------------------------------------
   Function streamCheckbox( cName, cValue, cText, lChecked )
      /*
          the Checkbox works similar to a dBASE checkbox,
          the 'value' is actually the text assigned to
          the checkbox. Note that the HTML checkbox 
          returns the value property, not a true or false
          property ... when checking your return values
          you would want to check for the value -- i.e.,
            if you pass a value property to this method
            of "A Test", and a name of "MyCheckbox", 
            if the checkbox is checked, the value returned is:
               oCGI["MyCheckbox"] = "A Test"
            OTHERWISE, the name is not returned at all -- 
            so your code would need to check for the existance
            of the key:
               if oCGI.isKey( "MyCheckbox" )
                  // it was checked and the value was passed on
               endif
          Parameters:
             cName --    as text control
             cValue --   value returned if checkbox was checked
             cText --    text that appears to the right of the 
                         Checkbox
             lChecked -- is this checkbox defaulting to a 'checked' 
                         state?
      */
      cOut = [<input type="CHECKBOX" name="]+cName+["]
      cOut+= [ value="]+cValue+["]
      if pCount() > 3 and lChecked 
         cOut+= [ checked]
      endif
      cOut+= [>]
      if pCount() > 2
         cOut+= cText
      endif

      this.streamDetail( cOut )
      this.streamDetail( [<!-- End of streamCheckbox -->] )
   // end of method: streamCheckbox

   // -----------------------------------------------------------------
   // streamReset - streams out RESET button form controls
   // -----------------------------------------------------------------
   Function streamReset( cText )
      /*
         The reset button simply clears out the values in
         an HTML form ...

         Parameters: cText -- text displayed on the button
                              this is optional ...
      */
      cOut = [<input type="RESET"]
      if pCount() => 1 // anything beyond 1 is ignored
         cOut+= [ value="]+cText+["]
      endif
      cOut+= [>]

      this.streamDetail( cOut )
      this.streamDetail( [<!-- End of streamReset -->] )
   // end of method: streamReset

   // -----------------------------------------------------------------
   // streamSubmit - streams out SUBMIT button form controls
   // -----------------------------------------------------------------
   Function streamSubmit( cText )
      /*
         The submit button submits the contents of the form ...

         Parameters: cText -- text displayed on the button
                              this is optional ...
      */
      cOut = [<input type="SUBMIT"]
      if pCount() => 1 // anything beyond 1 is ignored
         cOut+= [ value="]+cText+["]
      endif
      cOut+= [>]

      this.streamDetail( cOut )
      this.streamDetail( [<!-- End of streamSubmit -->] )
   // end of method: streamSubmit

   // -----------------------------------------------------------------
   // streamTextArea - streams out TextArea form controls
   // -----------------------------------------------------------------
   Function streamTextArea( cName, nRows, nCols, cValue )
      /*
          A textArea control in HTML is like an editor control
          In HTML this one actualy has two tags -- the second
          is used after the default value ...
          

          Parameters:
              cText -- as other controls except the buttons
                       reset/submit
              nRows -- this is the number of rows to take up
                       on a form -- it does not limit the
                       input, just the display
              nCols -- same as nRows -- how many columns to use.
              cValue -- optional, this is a default value if
                       adding data, or the contents of the field
                       you want to display ...
      */
      cOut = [<textarea name="]+cName+["]
      if pCount() > 1
         cOut+= [ rows=]+nRows
      endif
      if pCount() > 2
         cOut+= [ cols=]+nCols
      endif
      cOut+= [>]
      this.streamDetail( cOut )
      if pCount() > 3
         this.streamDetail( cValue )
      endif
      cOut = "</textarea>"
      this.streamDetail( cOut )
      this.streamDetail( [<!-- End of streamTextArea -->] )
   // end of method: streamTextArea

   // --------------------------------------------------
   // The select control is interesting and different
   // from all the other HTML controls ... takes
   // some extra work. For our purposes, it's easier
   // to break it out into parts, allowing you to define
   // your SELECT in as much detail as you need ...
   // ex:
   //     oCGI.streamSelectBegin( "MyList" )
   //     oCGI.streamOption( "Option1", "Display this", true )
   //     oCGI.streamOption( "Option2", "Display another" )
   //     oCGI.streamSelectEnd()
   // --------------------------------------------------
   Function streamSelectBegin( cName, nSize, lMultiple )
      /*
          This method starts the Select object, but you must
          also call the streamOption method for each item
          you wish to appear in your SELECT, and when done
          you must call the streamSelectEnd() method to
          stuff the </SELECT> tag into the form ... (without
          that tag your form is hosed!)

          Parameters:
             cName -- see other controls
             nSize -- if size is zero, or omitted, 
                      you will get a combobox. If
                      the size is larger than that,
                      you will get a listbox ...
             lMultiple -- allow multiple selections?
                      NOTE: If you select this, you 
                      will, by definition, get a listbox,
                      not a combobox ...
      */
      cOut = [<select name="]+cName+["]
      if pCount() > 1
         if nSize > 1
            cOut+= [ size=]+nSize
         endif
      endif
      if pCount() > 2 and lMultiple
         cOut+= [ multiple]
      endif
      cOut+= [>]
      this.streamDetail( cOut )
      this.streamDetail( [<!-- End of streamSelectBegin -->] )
   // end of method: streamSelectBegin

   // -----------------------------------------------------------------
   // streamOption - streams out OPTION form controls
   // -----------------------------------------------------------------
   Function streamOption( cValue, cText, lSelected )
      /*
          This method streams out the individual options that
          are needed for your select control.

          cValue -- is the value returned when the form is submitted
                    if this option is selected (required)
          cText  -- is the text displayed (it can be different from
                    the value) (required)
          lSelected -- is this selected when the form is displayed 
                    the first time?  (optional)
      */
      cOut = [<option value="]+cValue+["]
      if pCount() > 2 and lSelected
         cOut+= [ selected]
      endif
      cOut+= [>]
      this.streamDetail( cOut )

      this.streamDetail( cText )
      this.streamDetail( [<!-- End of streamOption -->] )
   // end of method: streamOption

   // -----------------------------------------------------------------
   // streamSelectEnd - streams out Select end form controls
   // -----------------------------------------------------------------
   Function streamSelectEnd
      // a simple method, this just makes sure we have
      // the end tag for the select option -- without it,
      // we have a messed up form ...
      this.streamDetail( "</select>" )
      this.streamDetail( [<!-- End of streamSelectEnd -->] )
   // end of method: streamSelectEnd()

   // -----------------------------------------------------------------
   // streamHidden - streams out Hidden form controls
   // -----------------------------------------------------------------
   Function streamHidden( cName, cValue )
      /*
         The 'Hidden' control for HTML forms is
         special -- the user never sees it, but it
         can be used to pass persistant values
         from one HTML form to another program;
         etc.

         Parameters:
           cName is the name -- if you want this to match a field,
                make sure you use *exact* case every time you 
                reference this, as the case is vital to the way 
                the web classes work (they are subclassed from 
                an Associative Array, and it is case sensitive).
           cValue is the value you want to 'Hide' -- could
                be the contents of the field
      */
      cOut = [<input type="HIDDEN" name="]+cName+["]
      cOut+= [ value="]+cValue+[">]
      this.streamDetail( cOut )
      this.streamDetail( [<!-- End of streamHidden -->] )
   // end of method: streamHidden

   // -----------------------------------------------------------------
   // errorMessage - create error message text stream out to current page
   // -----------------------------------------------------------------
   Function errorMessage( oErr, cTitleBackColor, cTitleTextColor )
      /*
          errorMessage was created after discovering that
          in some cases, an error caught by a try/catch
          will attempt to create a new page in the already
          streamed page, which causes one of two problems:
             1) It just plain looks wrong (the information
                streamed out in the streamHeader() messes things 
                up)
             2) The second body tag causes the browser to not 
                display anything at all ...

          The purpose of this method then, is to display an error
          after your header and body tags have been streamed
          out to your user. 

          I copied code from sorryPage() and errorPage()
          where it seemed appropriate ...

          Use: 
             After your form's header/body have been streamed
             out, wrap the rest of your program in a try/catch
             and when an error occurs, pass it to *this* method
             instead ...

          The parameters are:
          oErr: The error object generated. 
          cTitleBackColor: this is the background of the title
                 displayed across the top of the page --
                 if none is given, this method will use
                 the defaults set for the class.
          cTitleTextColor: this is the color of the text used
                 in the title at the top of the page ... as
                 above.

      */

      // make sure we have something besides a null for
      // the streamTitle() method:
      cTitleBackColor = iif( empty( cTitleBackColor), "", cTitleBackColor )
      cTitleTextColor = iif( empty( cTitleTextColor), "", cTitleTextColor )

      aError = new array()                // Create array for messages
      with (aError)
         add('File: '+oErr.Filename)     // Add file/line and message
         add('Line No:'+oErr.LineNo)
         add(''+oErr.Message)
      endwith

      cRecover = 'Contact the WebMaster'

      // stream title -- using the title, it will be big and bold
      // and grab someone's attention
      this.streamTitle( "Error!", cTitleBackColor, cTitleTextColor )

      this.streamDetail( '<p style="text-align:center;">' )
      ////// Traverse the message array
      for n = 1 to aLen(aError,1)
          this.streamDetail( aError[n] )
          this.streamDetail( '<br />' )
      next
      this.streamDetail( '</p>' )

      // stream out the recover message:
      this.streamDetail( '<p style="text-align:center;">'+cRecover+'</p>' )

      // stream the end of the HTML document
      this.streamFooter()

      // stop the application here
      quit
   // end of method: errorMessage

   // -----------------------------------------------------------------
   // errorPage - streams out default error page with error message text
   // -----------------------------------------------------------------
   Function errorPage( oErr, cTitleBackColor, cTitleTextColor )
      /*
          errorPage is modified in the same way that
          sorryPage is below ...

          The parameters are:
          oErr: The error object generated. 
          cTitleBackColor: this is the background of the title
                 displayed across the top of the page --
                 if none is given, this method will use
                 the defaults set for the class.
          cTitleTextColor: this is the color of the text used
                 in the title at the top of the page ... as
                 above.

          Note: this uses the sorryPage() method to display
          the error ...
      */

      // make sure we have something besides a 'null' to pass across
      // to sorryPage
      cTitleBackColor = iif( empty( cTitleBackColor), "", cTitleBackColor )
      cTitleTextColor = iif( empty( cTitleTextColor), "", cTitleTextColor )

      aError = new array()                // Create array for messages

      with (aError)
         add('File: '+oErr.Filename)     // Add file/line and message
         add('Line No:'+oErr.LineNo)
         add(''+oErr.Message)
      endwith

      cRecover = 'Contact the WebMaster'
     
      // Call sorryPage() to display  
      this.sorryPage(aError,'An error ocurred on the server',cRecover,;
                     cTitleBackColor, cTitleTextColor )

   return true
   // end of method: errorPage

   // -----------------------------------------------------------------
   // sorryPage - streams out warning page with message text
   // -----------------------------------------------------------------
   Function sorryPage(cMsg, cSubTtl, cRcvr, cTitleBackColor, cTitleTextColor )
      /*
          SorryPage is modified to use other methods here ...

          The parameters are:

          cMsg: this is the message you want to display
                for the user -- note that it can be an array --
                the message can then have multiple lines, and
                so on, and the message can have HTML embedded in it
          cSubTtl: this is the subtitle -- a title under the
                "sorry" heading
          cRcvr: this is the recovery message -- it explains
                 to the user what to do to recover from the
                 error ...
          cTitleBackColor: this is the background of the title
                 displayed across the top of the page --
                 if none is given, this method will use
                 the defaults set for the class.
          cTitleTextColor: this is the color of the text used
                 in the title at the top of the page ... as
                 above.

      */
  
      aMsg = iif(empty(cMsg),'',cMsg)

      if type('aMsg') = 'C'   // if message is not array,
         aMsg = new array()   // convert to a one-element array
         aMsg.add(cMsg)
      endif
                               // Make private versions for type()
      cRecover =  iif(empty(cRcvr),'Press Back button and try again.',cRcvr)  
      cSubtitle = iif(empty(cSubTtl),'',cSubTtl)

      /////// stream out header
      this.streamHeader('Sorry!')

      ////// Body tag (starts body of page)  
      this.streamBody()

      // stream out the title:
      cTitleBackColor = iif( empty( cTitleBackColor), this.TitleBackColor, cTitleBackColor )
      cTitleTextColor = iif( empty( cTitleTextColor), this.TitleTextColor, cTitleTextColor )
      this.streamTitle( "Sorry!", cTitleBackColor, cTitleTextColor )

      ////// Subtitle
      if not empty( cSubTitle )
         this.streamSubTitle( cSubTitle, cTitleBackColor, cTitleTextColor )
      endif
		
      ////// Traverse the message array
      cMsgStr = '<p style="text-align:center">'
      for n = 1 to aLen(aMsg,1)
          cMsgStr += aMsg[n]
			 cMsgStr += '<br />'
      next
      cMsgStr += '</p>'
      this.streamDetail( '<p style="text-align:center">' + cMsgStr + '</p>' )

      ////// If recovery instructions exist (Contact Webmaster, Press Back
      ////// 		Button, etc.), stream it here
      if not empty(cRecover)
         this.streamDetail( '<p style="text-align:center">' + cRecover + '</p>' )
      endif

      // and stream out the end of the HTML document
      this.streamFooter()

      // if we output this page, the mini-application is done: 
      quit

   // end of method: SorryPage


   ////// Method: 	streamHeader //////////////////////////////////////////////
   ////// Purpose		Streams the CGI and HTML headers required  ////////////////
   //////				to send a response page back through the server ///////////
   ////// Param:		Title - the page title desired ////////////////////////////

   Function streamHeader(Title)
      cTitle = iif(empty(Title),'Response Page',Title)

      this.streamDetail('Content-type: text/html')
      this.streamDetail('')
      this.streamDetail('<HTML>')
      this.streamDetail('')
      this.streamDetail('<HEAD>')
      this.streamDetail('	<META HTTP-EQUIV="Content-Type" CONTENT="text/html">')
      this.streamDetail('	<TITLE>'+cTitle+'</TITLE>')
      this.streamDetail('</HEAD>')
      this.streamDetail('')

      return true
  // End of streamHeader

  ////// Method: streamFooter /////////////////////////////////////////////////
  //////         Streams out closing tags for header/title block for //////////
  //////         HTML Response page sent back to user//////////////////////////

   Function streamFooter

      this.streamDetail(' ')
      this.streamDetail('   </body>')
      this.streamDetail(' ')
      this.streamDetail('</HTML>')

      return true
  // End of streamFooter


  ////// Method: 	streamResponsiveHeader //////////////////////////////////////////////
  ////// Purpose		Streams the CGI and HTML headers required  ////////////////
  //////				to send a response page back through the server ///////////
  ////// Param:		Title - the page title desired ////////////////////////////

   Function streamResponsiveHeader(Title)
      cTitle = iif(empty(Title),'Response Page',Title)

      this.streamDetail('<!DOCTYPE html>')
      this.streamDetail('<html lang="en">')
      this.streamDetail('<head>')
      this.streamDetail('  <meta charset="utf-8">')
      this.streamDetail('  <title>'+cTitle+'</title>')
      this.streamDetail('  <meta name="generator" content="dBASE PLUS 11 Web Wizard">')
      this.streamDetail('  <meta name="description" content="HTML output generated by dBASE PLUS 11 Web Wizard">')
      this.streamDetail('  <meta name="author" content="dBase LLC">')
      this.streamDetail('  <meta name="viewport" content="width=device-width, initial-scale=1">')
      this.streamDetail('  <link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">')
      this.streamDetail('  <link rel="stylesheet" href="css/normalize.css">')
      this.streamDetail('  <link rel="stylesheet" href="css/skeleton.css">')
      this.streamDetail('  <link rel="icon" type="image/x-icon" href="images/favicon.ico">')
      this.streamDetail('</head>')
      this.streamDetail('')

      return true
  // End of streamResponsiveHeader


  ////// Method: streamResponsiveSetup /////////////////////////////////////////////////
  //////         Streams out javascript to load resonsive CSS files must be placed below BODY tag //////////
  //////         when you can't have use streamResponsiveHeader, i.e. in a CGI program  //////////
  //////         HTML Response page sent back to user //////////////////////////

   Function streamResponsiveSetup

      this.streamDetail(" ")
      this.streamDetail([<script type="text/javascript">])
      this.streamDetail([function loadCSS(filename){])
      this.streamDetail([  var file = document.createElement("link");])
      this.streamDetail([  file.setAttribute("rel", "stylesheet");])
      this.streamDetail([  file.setAttribute("type", "text/css");])
      this.streamDetail([  file.setAttribute("href", filename);])
      this.streamDetail([  document.head.appendChild(file);])
      this.streamDetail("}")
      this.streamDetail([loadCSS("css/normalize.css");])
      this.streamDetail([loadCSS("css/skeleton.css");])
      this.streamDetail([</script>])
      this.streamDetail(" ")
	  
      this.streamDetail( [   <!-- End of streamResponsiveSetup -->] )
      return true
  // End of streamResponsiveSetup


  ////// Method: streamLogo /////////////////////////////////////////////////
  //////         Streams out logo for page  //////////
  //////         HTML Response page sent back to user //////////////////////////

   Function streamLogo(cLogo,cAlign)
   
      cLogo = iif(empty(cLogo), this.defaultLogo, cLogo)
      cAlign = iif(empty(cAlign), 'center',cAlign)

      ////// Setup Logo
      this.streamDetail([ ])
      this.streamDetail([   <div class="container">])
      this.streamDetail([     <div class="row">])
      this.streamDetail([       <div align="]+cAlign+["><img src="]+cLogo+["></div>])
      this.streamDetail([     </div>])
      this.streamDetail([   </div>])
      this.streamDetail([ ])

      this.streamDetail( [   <!-- End of streamLogo -->] )
      return true
  // End of streamLogo

  ////// Method: streamResponsiveCenter /////////////////////////////////////////////////
  //////         Streams out single block of text in center of page  //////////
  //////         HTML Response page sent back to user //////////////////////////

   Function streamResponsiveCenter(fText)
   
      ////// Setup responsive block to center
      this.streamDetail([ ])
      this.streamDetail([   <div class="container">])
      this.streamDetail([     <div class="row">])
      this.streamDetail([       <div align="center">]+fText+[</div>])
      this.streamDetail([     </div>])
      this.streamDetail([   </div>])
      this.streamDetail([ ])

      this.streamDetail( [   <!-- End of streamResponsiveCenter -->] )
      return true
  // End of streamResponsiveCenter

    /////////// Method:  HTMLColor ////////////////////////////////////
    /////////// Purpose: converts VdB 7 Color to HTML Color ///////////
    //////////           ex: 0x010203 to #030201 //////////////////////
    /////////// Param:   cColorStr - color expression /////////////////

   Function HTMLColor(cColorStr)
       if substr(cColorStr,1,2) = '0x'
          return '#'+substr(cColorStr,7,2)+;
                     substr(cColorStr,5,2)+;
                     substr(cColorStr,3,2)
       endif
       return cColorStr

   //
   // Simple function to take an existing SQL query and add a 
   // restriction clause, i.e. "where orderid > 5" to it and 
   // return it to the caller
   //
   Function modSQLQuery (oQuery, oClause, oLocal)
   
   Local returnString
   Local remainingString
   Local queryElements
   Local elementLocations
   Local singleTableName
   Local stopLocation
   Local startLocation
   
   returnString = ''
   
   if empty(oQuery)
      returnString += 'Error: SQL Query is missing'
      return returnString
   endif
   
   if empty(oQuery)
      returnString += 'Error: SQL Query restriction clause is missing'
      return returnString
   endif
   
   remainingString = oQuery
   queryElements = new AssocArray( )
   elementLocations = new AssocArray( )
   
   // Check for sections starting
   elementLocations ['ORDERBY'] = rat('ORDER BY',upper(oQuery))
   elementLocations ['HAVING'] = rat('HAVING',upper(oQuery))
   elementLocations ['GROUPBY'] = rat('GROUP BY',upper(oQuery))
   elementLocations ['WHERE'] = rat('WHERE',upper(oQuery))
   elementLocations ['FROM'] = rat('FROM',upper(oQuery))
   elementLocations ['SELECT'] = rat('SELECT',upper(oQuery))
   
   // process sections
   queryElements ['ORDERBY'] = ''
   if elementLocations ['ORDERBY'] > 0
      queryElements ['ORDERBY'] = substr(oQuery, elementLocations ['ORDERBY'])
      // ? queryElements ['ORDERBY']
      remainingString = substr (oQuery, 1, elementLocations ['ORDERBY']-1)
   endif
   
   queryElements ['HAVING'] = ''
   if elementLocations ['HAVING'] > 0
      queryElements ['HAVING'] = substr(remainingString, elementLocations ['HAVING'])
      // ? queryElements ['HAVING']
      remainingString = substr (remainingString, 1, elementLocations ['HAVING']-1)
   endif
   
   queryElements ['GROUPBY'] = ''
   if elementLocations ['GROUPBY'] > 0
      queryElements ['GROUPBY'] = substr(remainingString, elementLocations ['GROUPBY'])
      // ? queryElements ['GROUPBY']
      remainingString = substr (remainingString, 1, elementLocations ['GROUPBY']-1)
   endif
   
   queryElements ['WHERE'] = ''
   if elementLocations ['WHERE'] > 0
      queryElements ['WHERE'] = substr(remainingString, elementLocations ['WHERE'])
      // ? queryElements ['WHERE']
      remainingString = substr (remainingString, 1, elementLocations ['WHERE']-1)
   endif
   
   queryElements ['FROM'] = ''
   if elementLocations ['FROM'] > 0
      queryElements ['FROM'] = substr(remainingString, elementLocations ['FROM'])
      // ? queryElements ['FROM']
      remainingString = substr (remainingString, 1, elementLocations ['FROM']-1)
   endif
   
   queryElements ['SELECT'] = ''
   if elementLocations ['SELECT'] > 0
      queryElements ['SELECT'] = substr(remainingString, elementLocations ['SELECT'])
      // ? queryElements ['SELECT']
   endif

   // return error if NOT Select statement or doesn't have FROM clause
   
   if elementLocations ['SELECT'] = 0
      returnString += 'Error: Not a SQL SELECT query'
      return returnString
   endif

   if elementLocations ['FROM'] = 0
      returnString += 'Error: SQL Query missing valid FROM clause'
      return returnString
   endif
   
   // add restricition to either HAVING clause or WHERE
   
   if elementLocations ['GROUPBY'] > 0 
      if elementLocations ['HAVING'] > 0
         queryElements ['HAVING'] += ' and ' + oClause + ' '
         // ? queryElements ['HAVING']
      else
         queryElements ['HAVING'] += ' HAVING ' + oClause + ' '
         // ? queryElements ['HAVING']
      endif
   elseif elementLocations ['WHERE'] > 0 
      queryElements ['WHERE'] += ' and ' + oClause + ' '
      // ? queryElements ['WHERE']
   else
      queryElements ['WHERE'] += ' WHERE ' + oClause + ' '
      // ? queryElements ['WHERE']
   endif
   
   // localize table if desired
   
   if oLocal
      if elementLocations ['WHERE'] > 0
         stopLocation = elementLocations ['WHERE'] - 2
      elseif elementLocations ['GROUPBY'] > 0
         stopLocation = elementLocations ['GROUPBY'] - 2
      elseif elementLocations ['ORDERBY'] > 0
         stopLocation = elementLocations ['ORDERBY'] - 2
      else
         stopLocation = LEN(oQuery)
      endif
      
      startLocation = elementLocations ['FROM']+6 // take away the first single or double quote around the db string
      singleTableName = substr(oQuery, startLocation, stopLocation - startLocation)
      // ? singleTableName
      singleTableName = substr(singleTableName,rat('\',singleTableName)+1)
      // ? singleTableName
      
      // reconstruct the FROM clause
      queryElements ['FROM'] = 'FROM "' + singleTableName + '" '
   endif
   
   // reset elementLocation for WHERE or HAVING clauses that have been modified
   elementLocations ['WHERE'] = len (queryElements ['WHERE'])
   elementLocations ['HAVING'] = len (queryElements ['HAVING'])
   
   // recontruct the entire revised SQL query and return it
   returnString = queryElements ['SELECT'] + queryElements ['FROM']
   if elementLocations ['WHERE'] > 0 
      returnString += queryElements ['WHERE']
   endif
   if elementLocations ['GROUPBY'] > 0 
      returnString += queryElements ['GROUPBY']
   endif
   if elementLocations ['HAVING'] > 0 
      returnString += queryElements ['HAVING']
   endif
   if elementLocations ['ORDERBY'] > 0 
      returnString += queryElements ['ORDERBY']
   endif
   
   return returnString
	
   // end of function modSQLQuery
		   

  ////// Method: PassDataThrough ///////////////////////////////////////////////
  ////// 		  Passes through all received CGI data to the next form /////////

  /* Note: This method is used to "chain" Web pages together. For example,
     let's assume that you have an order page with a product lookup. Web pages
     can't go "back" unless the user clicks the Back button on the browser.
     So you -emulate- Windows-style behavior by calling the Product Lookup,
     then  a -new- copy of the invoice form with the lookup product code
     defaulted in.

     To get the data from the first Invoice Form, through the lookup form to
     the next version of the Invoice form, you have to pass along -every single
     bit of data that came in from the first form - to the Product Lookup form
     and then as controls to the final Invoice form.

     This method creates "hiddens" in the current HTML response page for all
     valid elements of this array. That data will then be sent automatically
     by the Web Browser to the next Web applet called from the page that
     carries the "pass through data". This method automatically generates the
     "hidden" HTML code required to pass a whole form of data to the next...
     and the next... etc.

     Usage: Call this method any time after streamBody() and before
     			streamFooter()

  */

   Function PassDataThrough

     cKey  = this.firstKey

     for n = 1 to this.count()

        this.fOut.Puts('<input type="HIDDEN" name="'+cKey+'" value="'+this[cKey]+'">')

        cKey = this.nextKey(cKey)

     next



  ////////////////////////// Database Classes //////////////////////////////////

   ////// Method: 	LoadArrayFromFields //////////////////////////////////
   ////// Purpose:	Loads existing assoc array with data from fields ///
   //////  				in a rowset passed as a param //////////////////////
   ////// Params:	   rowsetFields = query.rowset.fields object ref //////


   /* Note: All data methods in this class are based on name-matching.
   			The HTML component name that sent the data must be an
            identical match to the name of the field, including CASE.
            In other words, name your city Entryfield CITY if the field
            in your table is named CITY! If you do this unfailingly, you
            can totally automate CGI reads into table fields and
            table fields out to HTML response pages.
   */

   Function LoadArrayFromFields(rowsetFields)

      private rf, cKey
      local n

      rf = rowsetFields  // make "private" version of param

      cKey = this.FirstKey

      for n = 1 to this.count() // traverse this array

         // see if there's a field that matches the array index key
         // and see if it's empty or not.

         // if field exists
         if type('rf[cKey]') = 'O'

            // if field is not empty
            if empty(rf[cKey].value)

               this[cKey] = ""

            else

               // figure out its type and store it as value to array
               if rf[cKey].type = 'AUTOINC' or ;
                  rf[cKey].type = 'NUMERIC'  or ;
                  rf[cKey].type = 'LONG' or ;
                  rf[cKey].type = 'INTEGER'  or ;
                  rf[cKey].type = 'DOUBLE' or ;
                  rf[cKey].type = 'FLOAT'

                  this[cKey] = str(rf[cKey].value,;
                                   rf[cKey].length,;
                                   rf[cKey].decimalLength)

               elseif rf[cKey].type = 'DATE'
                   this[cKey] = dtoc(value)

               elseif rf[cKey].type = 'LOGICAL'
                   this[cKey] = iif(value,'TRUE','FALSE')

               elseif rf[cKey].type = 'BINARY' or ;
                      rf[cKey].type = 'OLE'
                   // do nothing

               else // char or memo
                   this[cKey] = rf[cKey].value

               endif

            endif

         endif

         cKey = this.nextKey(cKey)

      next

      return true



   ////// Method 	LoadFieldsFromArray ///////////////////////////////////////
   ////// Purpose: 	load data from assoc array into rowset fields /////////////
   ////// Params: 	rowset.fields /////////////////////////////////////////////

   /*      Note: This method may be called over and over again for any number
            of queries or rows within a query. It is, however, indiscriminate.
            If you run this for more than one rowset, it will attempt to
            update each and every matching field each time you run it.
            This may present a problem if you have two tables and you only
            want to update certain fields in one table and certain fields
            in the other.

            The field-related methods of this Web Class assume either:

      		1.) Only one table is in use or
            2.) Each table has unique field names or
            3.) It's OK if more than one table has the same
            	 field name and gets the same data.

            If you need to exercize care in opdating fields in one rowset
            but not in another, (for example customer->Custno and not
            invoice->Custno), we recommend you use the subclass:
            WebDMDClass.cc,, which lets you indicate both query and fieldname,
            not just fieldname as this method does. This method should
            accomodate most circumstances.
    */

   Function loadFieldsFromArray(rowsetFields,bAppend)

      bAppend = iif(empty(bAppend),false,true)

      rf = rowsetFields  // Create private instance of param

      ////// If bAppend, make new row
      if bAppend
         rf.parent.beginAppend()
      endif

      ////// Establish first key
      cKey = this.firstKey

      ///// Traverse the array
      for n = 1 to this.count()

         if type('rf[cKey]') = 'O'  // if field exists

            with (rf[ckey])         // with (field)

               if type  = "LOGICAL"   // convert text to type and store

                  value = iif(upper(this[cKey]) ='TRUE' or ;
                           upper(this[cKey]) = 'Y',true,false)

               elseif type = "NUMERIC" or ;
                  type = "INTEGER" or ;
                  type = "DOUBLE" or ;
                  type = "FLOAT" or ;
                  type = "LONG"

                  value = val(this[cKey])

               elseif type = "DATE"

                  value = ctod(this[cKey])

               elseif type = "AUTOINC" or ;
                      type = 'TIMESTAMP' or ;
                      type = 'BINARY' or ;
                      type = 'OLE'

                    // do nothing!! Autoincrement is automatic
                    // and the others not supported in HTML

               else // Char or memo

                  value = this[cKey]

               endif

            endwith

         endif
         ////// Go to next key
         cKey = this.nextKey(cKey)

      next

      if not rf.parent.endOfSet // if not end of set
         rf.parent.save()       // save row
      endif

      return true



   ////// Method: loadDatamoduleFromArray ////////////////////////////
   //////         loads data in this array into the rowset.fields ////
   //////         of the datamodule
   ////// Param:	oDataMod - a reference to a DataModule object ///////
   ////// 			bAppend - create new row?

   Function loadDataModuleFromArray(oDataMod,bAppend)

      if empty(oDataMod)
         return false // no datamodule to work with
      endif

      oD = oDataMod  // "Privatize param"  for use in macros

                     // ensure default of bAppand
      bAppend = iif(empty(bAppend),false,bAppend)

      ////// This array will track which queries have already
      ////// been appended so we don't create extra rows  or
      ////// save rows unneccessarily

      aAppends = new array()

      ////// Traverse this array of field/value pairs and update
      ////// rowset fields with data

      cKey = this.firstKey  // init first key


      For n = 1 to this.Count()  // traverse entire array

         if at('*@',cKey) = 0    // if not a datamod pair
            cKey = this.nextKey(cKey)
            loop                 // do next one
         endif

         ////// extract query name
         cQuery = substr(cKey,1,at("*@",cKey)-1)


         ////// Prepare macro to test query
         cStr = 'oD.'+cQuery

         ////// See if query exists
         Try
            oQuery = &cStr.
         catch (exception e)
            // this query does not exist!

            cKey = this.nextKey(cKey)
            loop
         endTry


         if type('oQuery.rowset') # 'O'
            // Rowset not valid???
            cKey = this.nextKey(cKey)
            loop
         endif

         ////// See if this query has been appended or edited
         ////// if not, append row if bAppend = true
         if aAppends.scan(upper(cQuery))  = 0

            aAppends.add(upper(cQuery)) // add to query array

            if bAppend
               oQuery.rowset.beginAppend()
            endif

         endif

         ////// extract field name
         cField = substr(cKey,at("*@",cKey)+2)

         if type('oQuery.rowset.fields[cField]') # 'O' // if no such field exists

            // loop and try the next
            cKey = this.nextKey(cKey)
            loop
         endif

         with (oQuery.rowset.fields[cField])       // with (field)

            ////// Convert from string to type and update field value

            if type 	= "LOGICAL"

               value  = ;
                  iif(upper(this[cKey]) = 'TRUE' or upper(this[cKey]) = 'Y',;
                  true,false)

            elseif type = "NUMERIC" or ;
               type = "INTEGER" or ;
               type = "LONG" or ;
               type = "DOUBLE" or ;
               type = "FLOAT"

               value = val(this[cKey])

            elseif type = "DATE"

               value = ctod(this[cKey])

            elseif type = 'AUTOINC' or ;
                   type = 'TIMESTAMP'  or ;
                   type = 'BINARY' or ;
                   type = 'OLE'

               	// do nothing, not supported in HTML


            else 	// char or memo

               value = this[cKey]

            endif

         endwith

         cKey = this.nextKey(cKey)

      Next

      ////// Traverse all queries and save rows
      for n = 1 to alen(aAppends,1)
         cQuery = aAppends[n]

         cStr = 'oD.'+cQuery  // Prepare string for macro

         try
            oQuery = &cStr.   // Get reference for this query
         catch (exception e)
            loop              // if fail, go to next
         endTry

         if not oQuery.rowset.endOfSet
            oQuery.rowset.save() // save row.
         endif

      next


EndClass

