Location: PHPKode > projects > dizzyPages > dizzypages/include/DzePage.class.php
<?php
/*
* Copyright (c) 2004, Doron Enav, dizzyPages
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name dizzyPages nor the names of its contributors may be used
 *   to endorse or promote products derived from this software without specific
 *   prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * dizzyPages application framework
 *
 * @package dizzyPages
 * @copyright dizzyPages
 * @author Doron Enav <hide@address.com>
 */
/**
 * DzePage
 *
 * The top level container of all functionality required to
 * process a dizzyPage.  This includes SQL connectivity, form processing,
 * building output page, and handling session and user persistent data.
 *
 * @author Doron Enav
 */
class DzePage
{
   /**
    * @var object DzeMySQL handle to current sql transaction
    */
   public $sql;
   private $userId;
   private $userName;
   private $loginApp;
   private $config;
   private $pageTs;
   private $pageId;
   private $isStandAlone;
   private $sesId;
   private $sesExtId;
   private $sesName;
   private $url;
   private $urlBase;
   private $urlExt;
   private $urlIsPublic;
   private $urlApp;
   private $urlAppTitle;
   private $isRedirect;
   private $isRedirected;
   private $referrerFormName;
   private $previousUrl;
   private $previousUrlIsPublic;
   private $previousUrlApp;
   private $previousFormErr;
   private $previousFormErrTrap;
   private $previousFormName;
   private $securityPass;
   private $isFormError;
   private $vpi;

   /**#@+
    * @internal
    * @access private
    */

   /**
    * Create an instance of a DzePage.
    *
    * Every page has a DzePage instance <b>$this</b> created by default. 
    * You never call this constructor method but you can access all of it's
    * public methods through the default object instance.
    */
   public function __construct($config) {
      $this->config = $config;
 
      $this->sql = new DzeMySQL($config);
      $this->sql->beginWork();

      $this->isRedirect = $this->getRequest("redirect");
      $this->isRedirected = 'N';

      $this->setPageVars();
      $this->initSession();
      if ($this->isStandAlone != "Y") $this->setPreviousPageVars();
      $this->setUrlAppTitle();

      $this->initSecurity();
   }

   public function close() {
      if ($this->isRedirect == 'Y')
         $this->setSesAttrib("common-SESSION-INVALID_NAV", "N");
      if ($this->isStandAlone != "Y")
         $this->setSesAttrib("common-SESSION-PAGE_ID", $this->pageId);
      $this->sql->commitWork();
      $this->sql->closeDB();
   }

   private function setPageVars() {
      // PHP _REQUEST looks for both get and post form submitions
      $this->referrerFormName = $this->getRequest("__DZE__FORM_NAME");

      $this->url = $this->getRequest("url");
      if (! $this->url)
         $this->url =   "/" . $this->config['default_application']
                      . "/_no_url_supplied_in_search_string_";
      $this->isStandAlone = $this->getPage($this->url, "STAND_ALONE_B");
      $this->urlExt = strrchr($this->url, ".");
      $this->urlBase = substr($this->url, 0, strlen($this->url) - strlen($this->urlExt));
      $this->urlApp = $this->getAppFromUrl($this->url);
      if (! $this->urlApp) $this->urlApp = $this->config["default_application"];
      $this->urlIsPublic = $this->isUrlPublic($this->url);
   }

   private function setPreviousPageVars() {
      $this->previousUrl = $this->getSesAttrib("common-SESSION-PREVIOUS-URL");
      $this->previousUrlApp = $this->getAppFromUrl($this->previousUrl);
      $this->previousUrlIsPublic = $this->isUrlPublic($this->previousUrl);

      $this->previousFormErr = $this->getSesAttrib("common-SESSION-PREVIOUS-FORM_ERR");
      $this->previousFormName = $this->getSesAttrib("common-SESSION-PREVIOUS-FORM_NAME");
      if ($this->previousFormErr == 'Y') {
         $this->previousFormErrTrap = $this->getFormTrapOnErr($this->previousFormName);
//         $this->previousActionUrl = $this->getSesAttrib("common-SESSION-PREVIOUS-ACTION_URL");
      }
   }

   private function setUrlAppTitle() {
      if ($this->urlApp == $this->previousUrlApp)
         $this->urlAppTitle = $this->getSesAttrib("common-ApplicationTitle");
      else {
         $appId = $this->getAppIdFromName($this->urlApp);
         $this->urlAppTitle = 
            $appId
               ? $this->getUserAttrib("common-ApplicationTitle", 1, $appId)
               : "";
         $this->setSesAttrib("common-ApplicationTitle", $this->urlAppTitle);
      }
   }

   private function getAppIdFromName($appMame) {
      $this->sql->pushResult();

      $this->sql->select(
         "DZE_USR",
         array("USR_ID"),
         array("LWR_CASE_NAME" => $appMame)
      );

      $usr = $this->sql->fetchAssoc();

      $this->sql->popResult();

      return ($usr ? $usr["USR_ID"] : 0);
   }

   private function isUrlPublic($url) {
      $basedir = explode('/', $url);
      return (isset($basedir[2]) && $basedir[2] == 'public') ? 'Y' : 'N';
   }

   private function getFormTrapOnErr($formName) {
      $this->sql->pushResult();

      $this->sql->select(
         "DZE_FORM",
         array("TRAP_ON_ERR_TYPE"),
         array("FORM_NAME" => $formName)
      );

      $row = $this->sql->fetchAssoc();

      $this->sql->freeResult();
      $this->sql->popResult();

      return $row["TRAP_ON_ERR_TYPE"];
   }

   private function getSecureString() {
      return sha1($this->pageTs . rand());
   }

   private function initSecurity() {
      $this->securityPass =    $this->getSesAttrib("common-SESSION-PAGE_ID")
                            == $this->getRequest("pageid");
      $this->pageId = $this->getSecureString();
   }

   private function initSession() {
      $this->sesName  = "id";
      $this->sesExtId = $this->getRequest($this->sesName);

      $this->sql->pushResult();

      $this->sql->select(
         "DZE_SESSION",
         array(
            "SESSION_USR_ID",
            "REGISTER_USR_ID",
            "LAST_HIT_TS",
            "TIME_OUT_AT_TS"
         ),
         array("SESSION_ID"  => $this->sesExtId),
         'FOR UPDATE'
      );

      $session = $this->sql->fetchAssoc();

      $this->sql->freeResult();

      if ($session) {
         $timeStamp = time();

         $this->sesId    = $session["SESSION_USR_ID"];
         $this->userId   = $session["REGISTER_USR_ID"];
         $this->pageTs   = $this->getNextPageTs($session, $timeStamp);
         $this->loginApp = $this->getSesAttrib("common-SESSION-APP-NAME");

         if (!$this->isSessionValid($session)) {
            $this->newSession();
         }
         else {
            $this->sql->update(
               "DZE_SESSION",
               array(
                  "LAST_HIT_TS"    => $this->pageTs,
                  "TIME_OUT_AT_TS" =>
                     gmstrftime(
                        "%Y-%m-%d %H:%M:%S",
                        $timeStamp + (60 * 60) // Time out in 60 minutes
                     ),
                  "LAST_PAGE_URL"  => $this->url
               ),
               array("SESSION_ID"  => $this->sesExtId)
            );

            if (!is_null($this->userId))
               $this->userName = $this->getSesAttrib("common-SESSION-USER-NAME");
         }
      }
      else {
         $this->newSession();
      }

      $this->sql->popResult();
   }

   private function isSessionValid($session) {
      $this->sql->pushResult();

      $isValid = true;

      // TODO set a session/user TIME OUT attribute for audit purpose
      // Check for logged in user's session timeout
      if (   ! is_null($this->userId)
          && $this->pageTs > $session["TIME_OUT_AT_TS"])
         $isValid = false;

      // Check for user logout
      if ($isValid && $this->getRequest("__DZE__LOGOUT") == "Y") {
         $this->setSesAttrib("common-SESSION-TS-LOGOUT", $this->pageTs);
         $isValid = false;
      }

      // remove invalid session
      if (! $isValid) {
         $this->sql->delete(
            "DZE_SESSION",
            array("SESSION_ID" => $this->sesExtId)
         );
      }

      $this->sql->popResult();

      return $isValid;
   }

   private function newSession() {
      $this->sql->pushResult();

      $timeStamp = time();
      $this->pageTs = gmstrftime("%Y-%m-%d %H:%M:%S", $timeStamp) . ".000";
      // Time out in 60 minutes
      $timeOutTs = gmstrftime("%Y-%m-%d %H:%M:%S", $timeStamp + (60 * 60));

      $this->sesExtId = md5($this->pageTs . rand());
      $this->sesId    = $this->sql->seqValue("DZE-SESSION_ID");
      $this->userId   = null;

      // TODO time out should be user settable
      $this->sql->insert(
         "DZE_SESSION",
         array(
            "SESSION_ID"     => $this->sesExtId,
            "SESSION_USR_ID" => $this->sesId,
            "LAST_HIT_TS"    => $this->pageTs,
            "TIME_OUT_AT_TS" => $timeOutTs,
            "LAST_PAGE_URL"  => $this->url
         )
      );

      // TODO set a session/user TIME OUT attribute for audit purpose
      // cleanup timed out sessions
      $this->sql->delete(
         "DZE_SESSION",
         array("1" => "1"),
         "and TIME_OUT_AT_TS < '" . $this->pageTs . "'"
      );

      // no longer using PHP sessions
      //$_SESSION["session_user_id"] = $this->sesId;

      $this->sql->delete("DZE_USR_ATTRIB_ERR",
         array("USR_ID" => $this->sesId));
      $this->sql->delete("DZE_USR_FORM_ERR",
         array("USR_ID" => $this->sesId));
      $this->sql->delete("DZE_USR_ATTRIB", 
         array("USR_ID" => $this->sesId));
      $this->setSesAttrib("common-SESSION-TS-START", $this->pageTs);

      // time out moved to DZE_SESSION table
      // $this->setSesAttrib("common-SESSION-TIMEOUT-MINUTES", "60");

      $this->sql->popResult();
   }

   private function getNextPageTs($session, $timeStamp) {
      $this->sql->pushResult();

      $pageTs = gmstrftime("%Y-%m-%d %H:%M:%S", $timeStamp);

      // Timestamp format 'YYYY-MM-DD hh:mm:ss.cnt"
      // hh - is 00-23 hours
      // cnt - is not a fractional second rather a 3 digit number that
      //   counts a page hit within a second.  This insures uniqnes on
      //   every page hit.
      $tsLastHit = $session["LAST_HIT_TS"];

      // If a unique registered user has more than one session make sure the
      // last hit ts is the highest amongst all the users session
      // before adjusting the current page ts to be unique
      if (!is_null($this->userId)) {
         $this->sql->select(
            "DZE_SESSION",
            array("LAST_HIT_TS"),
            array("REGISTER_USR_ID" => $this->userId),
            " and LAST_HIT_TS > '" . $tsLastHit . "'"
         );
         if ($ts = $this->sql->fetchAssoc())
            $tsLastHit = $ts["LAST_HIT_TS"];

         $this->sql->freeResult();
      }

      // adjust current page ts if it has already been used by a previous page
      list($tsLastHitSeconds, $tsLastHitCount) = explode(".", $tsLastHit);
      if ($tsLastHitSeconds == $pageTs)
         $pageTs = $tsLastHitSeconds . "."
            . sprintf("%03d", ($tsLastHitCount + 1));
      else $pageTs .= ".000";

      $this->sql->popResult();

      return $pageTs;
   }

   private function copyUserErrorToSession() {
      $this->sql->pushResult();

      $isError = $this->getUserAttrib("common-SESSION-PREVIOUS-FORM_ERR");

      if ($isError == "Y") {
         $this->setSesAttrib("common-SESSION-PREVIOUS-FORM_ERR", "Y");
         $this->setSesAttrib("common-SESSION-PREVIOUS-FORM_NAME",
            $this->getUserAttrib("common-SESSION-PREVIOUS-FORM_NAME"));

         $previousUrl = $this->getUserAttrib("common-SESSION-PREVIOUS-URL");
         $this->setSesAttrib("common-SESSION-PREVIOUS-URL", $previousUrl);
         $this->redirect($previousUrl);

         $this->sql->select(
            "DZE_USR_ATTRIB_ERR",
            array("ATTRIB_NAME", "REC_ID", "ERR_TXT"),
            array("USR_ID" => $this->userId)
         );

         while ($error = $this->sql->fetchAssoc())
            $this->setSesAttribError(
               $error["ATTRIB_NAME"],
               $error["ERR_TXT"],
               $error["REC_ID"]
            );

         $this->sql->freeResult();
      }

      $this->sql->popResult();
   }

   private function setReqPageErrs() {
      // TODO currently run on every page I believe this only needs to be run
      // on pages the have a form with escape on error set to off
      // TODO this needs a good cleanup
      $errAttribInFromFound = false;

      $this->sql->pushResult();

      $this->sql->select(
         "DZE_FORM_IN_PAGE",
         array("FORM_NAME"),
         array("PAGE_URL" => $this->url),
         "ORDER BY DISPLAY_ORDER"
      );

      while (   (! $errAttribInFromFound)
             && ($form_in_page = $this->sql->fetchAssoc())) {

         $this->sql->pushResult();

         $this->sql->select(
            "DZE_FORM",
            array("FORM_NAME", "ACTION_URL", "TRAP_ON_ERR_TYPE"),
            array(
               "FORM_NAME"    => $form_in_page["FORM_NAME"],
               "TYPE"         => "S"
            ),
            "and TRAP_ON_ERR_TYPE <> 'N'"
         );

         if ($form = $this->sql->fetchAssoc()) {
            $this->sql->pushResult();

            $this->sql->select(
               "DZE_ATTRIB_IN_FORM",
               array("ATTRIB_NAME"),
               array("FORM_NAME" => $form["FORM_NAME"])
            );

            while ($attrib_in_form = $this->sql->fetchAssoc()) {
               $this->sql->pushResult();

               $this->sql->select(
                  "DZE_ATTRIB",
                  array("ATTRIB_NAME", "SAVE_TYPE", "REQUIRED_B", "DFLT_VALUE"),
                  array("ATTRIB_NAME" => $attrib_in_form["ATTRIB_NAME"])
               );

               if ($attrib = $this->sql->fetchAssoc()) {
                  $this->sql->pushResult();

                  switch ($attrib["SAVE_TYPE"]) {
                     case "S":
                        $usr_value = $this->getSesAttrib($attrib["ATTRIB_NAME"]);
                        break;
                     case "U":
                        $usr_value = $this->getUserAttrib($attrib["ATTRIB_NAME"]);
                        break;
                     default:
                        $usr_value = null;
                  }

                  if (   is_null($usr_value)
                      && strlen($attrib["DFLT_VALUE"]) > 0) {
                     $usr_value = $attrib["DFLT_VALUE"];
                  }

                  $error = array();

                  $this->sql->select(
                     "DZE_ATTRIB_RULE",
                     array("RULE", "FIRE_ORDER", "RULE_TYPE", "ERR_TXT"),
                     array("ATTRIB_NAME" => $attrib["ATTRIB_NAME"]),
                     "ORDER BY FIRE_ORDER"
                  );

                  $error['found'] = false;
                  while (   ($rule = $this->sql->fetchAssoc())
                         && (! $error['found'])
                        ) {
                     $error['found'] = ereg($rule["RULE"], $usr_value);
                     if ($rule["RULE_TYPE"] != "M") $error['found'] = ! $error['found'];
                     if ($error['found']) {
                        $error['found'] = 'Y';
                        $error['text'] = $rule["ERR_TXT"];
                     }
                  }

                  if ($error['found']) {
                     $this->setSesAttribError($attrib["ATTRIB_NAME"], $error['text']);
                     if ($form["TRAP_ON_ERR_TYPE"] == "U")
                        $this->setUserAttribError($attrib["ATTRIB_NAME"], $error['text']);
                     $errAttribInFromFound = true;
                  }
                  $this->sql->freeResult();
                  $this->sql->popResult();
               }
               $this->sql->freeResult();
               $this->sql->popResult();
            }
            $this->sql->freeResult();
            $this->sql->popResult();
         }
         $this->sql->freeResult();
         $this->sql->popResult();

         if ($errAttribInFromFound) {
            $this->setSesAttrib("common-SESSION-PREVIOUS-FORM_ERR", "Y");
            $this->setSesAttrib("common-SESSION-PREVIOUS-FORM_NAME", $form["FORM_NAME"]);
//            $this->setSesAttrib("common-SESSION-PREVIOUS-ACTION_URL", $form["ACTION_URL"]);
            if ($form["TRAP_ON_ERR_TYPE"] == "U") {
               $this->setUserAttrib("common-SESSION-PREVIOUS-FORM_ERR", "Y");
               $this->setUserAttrib("common-SESSION-PREVIOUS-FORM_NAME", $form["FORM_NAME"]);
               $this->setUserAttrib("common-SESSION-PREVIOUS-URL", $this->url);
            }
         }
      }

      $this->sql->freeResult();
      $this->sql->popResult();
   }

   public function getRequest($requestName) {
      return
         (isset($_REQUEST[$requestName])
            ? $_REQUEST[$requestName] : null);
   }

   public function getAppFromUrl($url) {
      $app = explode('/', $url);
      return isset($app[1]) ? $app[1] : null;
   }

   public function setVpi($name, $value) {
      $this->vpi[$name] = $value;
   }

   public function isValidNavigation() {
      $retValue = true;

      if ($this->getSesAttrib("common-SESSION-INVALID_NAV") == "Y") {
         $retValue = false;
      }

      return ($retValue);
   }

   public function getMember($memberName) {
      eval ('$memeber = $this->' . $memberName . ';');
      return $memeber;
   }

   public function setFormError($isError = null) {
      if (! is_null($isError))
         $this->isFormError = $isError;
      return $this->isFormError;
   }

   public function processPage() {
      $setReqPageErrs = false;

      // Do not process form
      // If     prvious page is not public
      //    and (   user is not logged in
      //         or previous page does not belong to the logged in app
      //         or previous page is not being run inside the CASE tool)
      if (   $this->previousUrlIsPublic != 'Y'
          && (   is_null($this->userId)
              || (   $this->loginApp != 'dze'
                  && $this->loginApp != $this->previousUrlApp)
              || (   $this->loginApp == 'dze'
                  && $this->userName != $this->previousUrlApp
                  && $this->loginApp != $this->previousUrlApp))) {
         dzeLog("--->1 Do not process form\n");
         // Do not process form
      }


      // Redirect to previous page
      // If     not already performing redirect
      //    and previous page/form contains an outstanding error
      //    and previous page/form is trapping
      //    and submitted form is not the form with the outstanding error
      else if (   $this->isRedirect != 'Y'
               && $this->previousFormErr == 'Y'
               && $this->previousFormErrTrap != 'N'
               && $this->previousFormName != $this->referrerFormName) {
         dzeLog("--->2 Redirect to previous page\n");
         $this->redirect($this->previousUrl, "Y", "Y");
      }


      // Proceed to process form
      // If    previous form has no errors
      //    or (    previous form has errors
      //        and submitted form is the previous form with the outstanding error)
      else if (   $this->previousFormErr != 'Y'
               ||    $this->previousFormErr == 'Y'
                  && $this->previousFormName == $this->referrerFormName) {
         dzeLog("--->3 Proceed to process form\n");

         // Process form
         // If a form has been submitted
         if ($this->referrerFormName) {
            // If form has been submitted in the expected order
            if ($this->securityPass) {
	       
               $phpFile = $this->config["directory"] . "/formprocess" . substr($this->previousUrl, 0, strlen($this->previousUrl) - 4) . ".php";
               if (!file_exists($phpFile))
                  $phpFile = $this->config["directory"] . "/formprocess/default.php";

               dzeLog("--->4 Process form - " . $phpFile . "\n");
               include $phpFile;

               if ($this->loginApp == 'dze' || $this->config['log_level'] >= 5)
                  $this->vpi['formPhpFile'] = $phpFile;

               // Redirect to previous page after processing form
               // If     current form contains errors
               //    and form originating page is not the current page
               if (   $this->isFormError == 'Y'
                   && $this->previousUrl != $this->url) {
                  dzeLog("--->5 Redirect to previous page " . $this->previousUrl . " Due to outstanding error.\n");
                  $this->redirect($this->previousUrl);
               }
            }
            // else the form has NOT been submitted in the expected order
            // This happens when the user uses the browser back button to
            // navigate
            else {
               dzeLog("--->6 Redirect to previous page " . $this->previousUrl . " Due to illegal user navigation.\n");
               $this->redirect($this->previousUrl, "Y", "Y");
            }
         }
         // No form submitted or form submitted out of order
         else {
            dzeLog("--->7 No form submitted\n");
            // TODO test this with two Standard forms
            // TODO I think this only needs to be run for form with no escape
            $setReqPageErrs = true;
         }
      }



      // Escape occured clearing outstanding errors
      // If     user is escaping
      //    and exists outstanding errors
      //    and page/form is not trapping
      else if (   $this->previousUrl != $this->url
               && $this->previousFormErr == 'Y'
               && $this->previousFormErrTrap == 'N') {
         dzeLog("--->8 Escape occured clearing outstanding errors\n");
         // clear outstanding errors so that the new page/form does not display
         // erroneous errors form the escaped page/form
         $this->clearErrors($this->sesId);
      }


      // Redirect page to public/home.dze
      // If     not already performing redirect
      //    and page is not public
      //    and (   user is not logged in
      //         or page does not belong to the logged in app
      //         or page is not being run inside the CASE tool)
      if (   $this->isRedirected != 'Y'
          && $this->urlIsPublic != 'Y'
          && (   is_null($this->userId)
              || (   $this->loginApp != 'dze'
                  && $this->loginApp != $this->urlApp)
              || (   $this->loginApp == 'dze'
                  && $this->userName != $this->urlApp
                  && $this->loginApp != $this->urlApp))) {
         dzeLog(  "--->9 Redirect page to /"
                . ($this->loginApp == "dze" ? "dze" : $this->urlApp)
                . "/public/home.dze\n");
         $this->redirect(  "/"
                         . ($this->loginApp == "dze" ? "dze" : $this->urlApp)
                         . '/public/home.dze', "Y", "Y");
      }



      // Display page
      // If not performing redirect
      if ($this->isRedirected != 'Y') {
         $phpFile = $this->config["directory"] . "/pageoutput" . $this->urlBase . ".php";
         if (!file_exists($phpFile))
            $phpFile = $this->config["directory"] . "/pageoutput/default.php";

         dzeLog("--->10 Display page - " . $phpFile . "\n");
         include $phpFile;

         if ($this->loginApp == 'dze' || $this->config['log_level'] >= 5)
            $this->vpi['outputPhpFile'] = $phpFile;

         if ($this->isStandAlone != "Y")
            $this->setSesAttrib("common-SESSION-PREVIOUS-URL", $this->url);

         if ($setReqPageErrs) $this->setReqPageErrs();
      }
   }

   public function getPage($url, $fields = null) {
      $retValue = null;

      $this->sql->pushResult();

      if (is_null($fields))
         $fields = array(
            "TITLE",
            "STAND_ALONE_B",
            "DESCRIP"
         );

      $this->sql->select(
         "DZE_PAGE",
         (is_array($fields)) ? $fields : array($fields),
         array("PAGE_URL" => $url)
      );

      if (is_array($fields))
         $retValue = $this->sql->fetchAssoc();
      else {
         $row = $this->sql->fetchArray();
         if ($this->sql->dbCount > 0)
            $retValue = array_pop($row);
         else
            $retValue = $row;
      }

      $this->sql->freeResult();
      $this->sql->popResult();

      return $retValue;
   }

   public function setPage($url, $cols) {
      $this->sql->pushResult();

      $this->sql->select(
         "DZE_PAGE",
         array("'X'"),
         array("PAGE_URL" => $url),
         "FOR UPDATE"
      );

      if ($this->sql->fetchArray()) {
         $this->sql->update(
            "DZE_PAGE",
            $cols,
            array("PAGE_URL" => $url)
         );
      }
      else {
         $this->sql->insert(
            "DZE_PAGE",
            array_merge(
               array("PAGE_URL" => $url),
               $cols
            )
         );
      }

      $this->sql->popResult();
   }

   public function deletePage($url) {
      $this->sql->pushResult();

      $this->sql->delete(
         "DZE_FORM_IN_PAGE",
         array("PAGE_URL" => $url)
      );
      $this->sql->delete(
         "DZE_PAGE",
         array("PAGE_URL" => $url)
      );
      $this->sql->update(
         "DZE_FORM",
         array("ACTION_URL" => ""),
         array("ACTION_URL" => $url)
      );

      $this->sql->popResult();
   }

   public function getForm($name, $fields = null) {
      $this->sql->pushResult();

      if (is_null($fields))
         $fields = array(
            "TYPE",
            "METHOD",
            "TITLE",
            "ACTION_URL",
            "TRAP_ON_ERR_TYPE",
            "R_SAVE_TYPE",
            "R_AUDIT_TYPE",
            "R_ITEMS_MAX",
            "R_ITEMS_MAX",
            "DESCRIP"
         );

      $this->sql->select(
         "DZE_FORM",
         (is_array($fields)) ? $fields : array($fields),
         array("FORM_NAME" => $name)
      );

      if (is_array($fields))
         $retValue = $this->sql->fetchAssoc();
      else {
         $row = $this->sql->fetchArray();
         if ($this->sql->dbCount > 0)
            $retValue = array_pop($row);
         else
            $retValue = $row;
      }

      $this->sql->freeResult();
      $this->sql->popResult();

      return $retValue;
   }

   public function setForm($name, $cols) {
      $this->sql->pushResult();

      $this->sql->select(
         "DZE_FORM",
         array("'X'"),
         array("FORM_NAME" => $name),
         "FOR UPDATE"
      );

      if ($this->sql->fetchArray()) {
         $this->sql->update(
            "DZE_FORM",
            $cols,
            array("FORM_NAME" => $name)
         );
      }
      else {
         $this->sql->insert(
            "DZE_FORM",
            array_merge(
               array("FORM_NAME" => $name),
               $cols
            )
         );
      }

      $this->setAttrib(
         "__CT__" . $name,
         array(
            "TYPE"           => "T",
            "SAVE_TYPE"      => "S",
            "AUDIT_TYPE"     => "N",
            "REQUIRED_B"     => "N",
            "S_SLCT_DYN_B"   => "N",
            "DESCRIP"        => "DZE Internal maintains record count"
         )
      );

      if (isset($cols["TYPE"]) && $cols["TYPE"] == "R") {
         $delAttribName = "__DT__" . $name;
         $this->setAttrib(
            $delAttribName,
            array(
               "TYPE"              => "M",
               "SAVE_TYPE"         => "N",
               "AUDIT_TYPE"        => "N",
               "REQUIRED_B"        => "N",
               "S_SLCT_DYN_B"      => "N",
               "S_FORM_TYPE"       => "B",
               "S_FORM_ORDER_TYPE" => "H",
               "FORM_LABEL"        => "DELETE",
               "DESCRIP"           => "DZE Internal record delete indicator"
            )
         );

         $options = array();
         $options[1] =
            array("VALUE" => "Y", "FORM_LABEL" => "", "DEFAULT_B"  => "N");

         $this->setAttribOption($delAttribName, $options);
      }

      $this->sql->popResult();
   }

   public function deleteForm($name) {
      $this->sql->pushResult();

      $this->deleteAttrib("__CT__" . $name);
      $this->deleteAttrib("__DT__" . $name);
      $this->sql->delete(
         "DZE_ATTRIB_IN_FORM",
         array("FORM_NAME" => $name)
      );
      $this->sql->delete(
         "DZE_FORM_IN_PAGE",
         array("FORM_NAME" => $name)
      );
      $this->sql->delete(
         "DZE_FORM",
         array("FORM_NAME" => $name)
      );

      $this->sql->popResult();
   }

   public function getAttrib($name, $fields = null) {
      $this->sql->pushResult();

      if (is_null($fields))
         $fields = array(
            "TYPE",
            "SAVE_TYPE",
            "AUDIT_TYPE",
            "REQUIRED_B",
            "FORM_LABEL",
            "DFLT_VALUE",
            "T_FORM_TYPE",
            "T_FORM_LEN",
            "T_FORM_LEN_MAX",
            "T_AREA_FORM_ROWS",
            "S_FORM_TYPE",
            "S_FORM_COLS",
            "S_FORM_ORDER_TYPE",
            "S_SLCT_DYN_B",
            "M_SLCT_OPTION_MIN",
            "M_SLCT_OPTION_MAX",
            "P_ENCRYPT_B",
            "DESCRIP"
         );

      $this->sql->select(
         "DZE_ATTRIB",
         (is_array($fields)) ? $fields : array($fields),
         array("ATTRIB_NAME" => $name)
      );

      if (is_array($fields))
         $retValue = $this->sql->fetchAssoc();
      else {
         $row = $this->sql->fetchArray();
         if ($this->sql->dbCount > 0)
            $retValue = array_pop($row);
         else
            $retValue = $row;
      }

      $this->sql->popResult();

      return $retValue;
   }

   public function setAttrib($name, $cols) {
      $this->sql->pushResult();

      $this->sql->select(
         "DZE_ATTRIB",
         array("'X'"),
         array("ATTRIB_NAME" => $name),
         "FOR UPDATE"
      );

      if ($this->sql->fetchArray()) {
         $this->sql->update(
            "DZE_ATTRIB",
            $cols,
            array("ATTRIB_NAME" => $name)
         );
      }
      else {
         $this->sql->insert(
            "DZE_ATTRIB",
            array_merge(
               array("ATTRIB_NAME" => $name),
               $cols
            )
         );
      }

      $this->sql->popResult();
   }

   public function deleteAttrib($name) {
      $this->sql->pushResult();

      $this->sql->delete(
         "DZE_USR_ATTRIB_ERR",
         array("ATTRIB_NAME" => $name)
      );
      $this->sql->delete(
         "DZE_USR_ATTRIB",
         array("ATTRIB_NAME" => $name)
      );
      $this->sql->delete(
         "DZE_USR_ATTRIB_ADT",
         array("ATTRIB_NAME" => $name)
      );
      $this->sql->delete(
         "DZE_ATTRIB_RULE",
         array("ATTRIB_NAME" => $name)
      );
      $this->sql->delete(
         "DZE_SLCT_OPTION",
         array("ATTRIB_NAME" => $name)
      );
      $this->sql->delete(
         "DZE_ATTRIB_IN_FORM",
         array("ATTRIB_NAME" => $name)
      );
      $this->sql->delete(
         "DZE_ATTRIB",
         array("ATTRIB_NAME" => $name)
      );      

      $this->sql->popResult();
   }

   public function getFormAttrib($name) {
      $this->sql->pushResult();

      $attribS = array();
      $this->sql->select(
         "DZE_ATTRIB_IN_FORM",
         array("DISPLAY_ORDER", "ATTRIB_NAME"),
         array("FORM_NAME" => $name),
         " and ATTRIB_NAME not like '\_\_DT\_\_%' order by DISPLAY_ORDER"
      );

      $attribSCt = 0;
      while ($attrib = $this->sql->fetchAssoc())
         $attribS[++$attribSCt] = $attrib["ATTRIB_NAME"];

      $this->sql->freeResult();
      $this->sql->popResult();

      return $attribS;
   }

   public function setFormAttrib($name, $attribS) {
      $this->sql->pushResult();

      $this->sql->delete(
         "DZE_ATTRIB_IN_FORM",
         array("FORM_NAME" => $name)
      );

      foreach ($attribS as $displayOrder => $attribName)
         $this->sql->insert(
            "DZE_ATTRIB_IN_FORM",
            array(
               "FORM_NAME"     => $name,
               "ATTRIB_NAME"   => $attribName,
               "DISPLAY_ORDER" => $displayOrder
            )
         );

      // Record forms have an internal __DT__(delete) attribute
      if ($this->getForm($name, "TYPE") == "R")
         $this->sql->insert(
            "DZE_ATTRIB_IN_FORM",
            array(
               "FORM_NAME"     => $name,
               "ATTRIB_NAME"   => "__DT__" . $name,
               "DISPLAY_ORDER" => count($attribS) + 1
            )
         );

      $this->sql->popResult();
   }

   public function getPageForm($url) {
      $this->sql->pushResult();

      $formS = array();
      $this->sql->select(
         "DZE_FORM_IN_PAGE",
         array("DISPLAY_ORDER", "FORM_NAME"),
         array("PAGE_URL" => $url),
         " order by DISPLAY_ORDER"
      );

      $formSCt = 0;
      while ($form = $this->sql->fetchAssoc())
         $formS[++$formSCt] = $form["FORM_NAME"];

      $this->sql->freeResult();
      $this->sql->popResult();

      return $formS;
   }

   public function setPageForm($url, $formS) {
      $this->sql->pushResult();

      $this->sql->delete(
         "DZE_FORM_IN_PAGE",
         array("PAGE_URL" => $url)
      );

      foreach ($formS as $displayOrder => $formName)
         $this->sql->insert(
            "DZE_FORM_IN_PAGE",
            array(
               "PAGE_URL"      => $url,
               "FORM_NAME"     => $formName,
               "DISPLAY_ORDER" => $displayOrder
            )
         );

      $this->sql->popResult();
   }

   public function getAttribRule($name) {
      $this->sql->pushResult();

      $ruleS = array();
      $this->sql->select(
         "DZE_ATTRIB_RULE",
         array("FIRE_ORDER", "RULE", "RULE_TYPE", "ERR_TXT"),
         array("ATTRIB_NAME" => $name)
      );

      while ($rule = $this->sql->fetchAssoc())
         $ruleS[$rule["FIRE_ORDER"]] = array(
            "RULE"      => $rule["RULE"],
            "RULE_TYPE" => $rule["RULE_TYPE"],
            "ERR_TXT"   => $rule["ERR_TXT"]
         );

      $this->sql->freeResult();
      $this->sql->popResult();

      return $ruleS;
   }

   public function getAttribOption($name) {
      $this->sql->pushResult();

      $optionS = array();
      $this->sql->select(
         "DZE_SLCT_OPTION",
         array("DISPLAY_ORDER", "VALUE", "FORM_LABEL", "DEFAULT_B"),
         array("ATTRIB_NAME" => $name)
      );

      while ($option = $this->sql->fetchAssoc())
         $optionS[$option["DISPLAY_ORDER"]] = array(
            "VALUE"      => $option["VALUE"],
            "FORM_LABEL" => $option["FORM_LABEL"],
            "DEFAULT_B"  => $option["DEFAULT_B"]
         );

      $this->sql->freeResult();
      $this->sql->popResult();

      return $optionS;
   }

   public function setAttribRule($name, $ruleS) {
      $this->sql->pushResult();

      $this->sql->delete(
         "DZE_ATTRIB_RULE",
         array("ATTRIB_NAME" => $name)
      );

      foreach ($ruleS as $fireOrder => $rule)
         $this->sql->insert(
            "DZE_ATTRIB_RULE",
            array(
               "ATTRIB_NAME" => $name,
               "FIRE_ORDER"  => $fireOrder,
               "RULE"        => $rule["RULE"],
               "RULE_TYPE"   => $rule["RULE_TYPE"],
               "ERR_TXT"     => $rule["ERR_TXT"]
            )
         );

      $this->sql->popResult();
   }

   public function setAttribOption($name, $optionS) {
      $this->sql->pushResult();

      $this->sql->delete(
         "DZE_SLCT_OPTION",
         array("ATTRIB_NAME" => $name)
      );

      $defaultValue = array();
      // Mimics the blank entry in a form submittion for a Multi-select
      if ($this->getAttrib($name, 'TYPE') == 'M')
         array_push($defaultValue, "");

      foreach ($optionS as $displayOrder => $option) {
         $defaultB = substr($option["DEFAULT_B"], -1);
         if ($defaultB == 'Y') array_push($defaultValue, $option["VALUE"]);
         $this->sql->insert(
            "DZE_SLCT_OPTION",
            array(
               "ATTRIB_NAME"   => $name,
               "DISPLAY_ORDER" => $displayOrder,
               "VALUE"         => $option["VALUE"],
               "FORM_LABEL"    => $option["FORM_LABEL"],
               "DEFAULT_B"     => $defaultB
            )
         );
      }

      $defaultValue = array_unique($defaultValue);
      sort($defaultValue);
      $cols = array(
         "DFLT_VALUE" => implode("|", $defaultValue)
      );

      $this->setAttrib($name, $cols);

      $this->sql->popResult();
   }

   public function createVpi() {
      // TODO cleanup required
      
      // This happens when the user clicks logout from the CASE tool
      // with out this poor style of return several errors are reported
      if (   !$this->userName
          || (    $this->loginApp != 'dze'
              and $this->config['log_level'] < 5)
         ) return;
      if ($this->isRedirected == 'Y') return;
      
      $logDir = 
         $_SERVER["DOCUMENT_ROOT"] . "/dze/usr/" .
         ($this->loginApp
             ? ($this->loginApp == 'dze'
                   ? strtolower($this->userName)
                   : $this->loginApp)
             : $this->urlApp) .
         "/log";

      if (! isset($this->vpi['formPhpFile'])) {
         if ($this->isRedirect != 'Y') {
            $this->vpi['formPhpFile'] = 'No form processed';
            $logHandle = fopen($logDir . "/form.txt", "w");
            fwrite($logHandle, "empty ...");
            fclose($logHandle);
            $logHandle = fopen($logDir . "/attrib.txt", "w");
            fwrite($logHandle, "empty ...");
            fclose($logHandle);
         }
         else
            $this->vpi['formPhpFile'] = $this->config["directory"] . '/formprocess' . substr($this->previousUrl, 0, -3) . 'php';
      }
      else {
         if (strpos($this->vpi['formPhpFile'], '/formprocess/dze/')) {
            $logHandle = fopen($logDir . "/form.txt", "w");
            fwrite($logHandle, "dizzyPages internals code ...");
            fclose($logHandle);
         }
         else copy($this->vpi['formPhpFile'], $logDir . '/form.txt');
         $logHandle = fopen($logDir . "/attrib.txt", "w");
         fwrite($logHandle, isset($this->vpi['attrib']) ? $this->vpi['attrib'] : "");
         fclose($logHandle);
      }

      if (strpos($this->vpi['outputPhpFile'], '/pageoutput/dze/')) {
         $logHandle = fopen($logDir . "/dom.txt", "w");
         fwrite($logHandle, "dizzyPages internals code ...");
         fclose($logHandle);
      }
      else copy($this->vpi['outputPhpFile'], $logDir . '/dom.txt');
      copy($this->vpi['xslFile'], $logDir . '/page.xsl');
      $logHandle = fopen($logDir . "/xml.txt", "w");
      fwrite($logHandle, $this->vpi['xml']);
      fclose($logHandle);
      copy($logDir . '/xml.txt', $logDir . '/page.xml');

      $logHandle = fopen($logDir . "/vpi.html", "w");

      fwrite($logHandle,
'<HTML> 
  <HEAD> 
	 <TITLE>dizzyPages - View Page Internals</TITLE> 
  </HEAD> 
  <BODY> 
	 <FORM> 
		<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="0"> 
		  <TR>
			 <TD COLSPAN="2">dizzyPages View Page Internals</TD>
		  </TR>
		  <TR>
			 <TD COLSPAN="2">URL: ' . $this->url . '</TD>
		  </TR>
		  <TR>
			 <TD COLSPAN="2">At: ' . $this->pageTs . '</TD>
		  </TR>
		  <TR>
			 <TD COLSPAN="2">&nbsp;</TD>
		  </TR>
		  <TR> 
			 <TD COLSPAN="2"><A HREF="#form">Form Process PHP Class Source</A></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2"><A HREF="#attrib">Form Process Data</A></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2"><A HREF="#dom">Page Output PHP Class Source</A></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2"><A HREF="#xml">Page Output Dynamic XML</A></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2"><A HREF="#xsl">Page Output XSL Stylesheet</A></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2">&nbsp;</TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2">&nbsp;</TD> 
		  </TR> 
		  <TR> 
			 <TD><A NAME="form">Form Process PHP Class Source</A>:</TD>
			 <TD ALIGN="RIGHT"><INPUT TYPE="BUTTON" VALUE="Close Window"
				ONCLICK="self.close()"></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="3">' . $this->vpi['formPhpFile'] . '</TD>
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2"> 
				<TABLE BGCOLOR="#000000"> 
				  <TR> 
					 <TD BGCOLOR="#FFFFFF"><IFRAME SRC="form.txt"
						NORESIZE="NORESIZE" SCROLLING="AUTO" HEIGHT="400" WIDTH="600" BORDER="0"
						FRAMEBORDER="0"></IFRAME></TD> 
				  </TR> 
				</TABLE></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2">&nbsp;</TD> 
		  </TR> 
		  <TR> 
			 <TD><A NAME="attrib">Form Process Data</A></TD>
			 <TD ALIGN="RIGHT"><INPUT TYPE="BUTTON" VALUE="Close Window"
				ONCLICK="self.close()"></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2"> 
				<TABLE BGCOLOR="#000000"> 
				  <TR> 
					 <TD BGCOLOR="#FFFFFF"><IFRAME SRC="attrib.txt"
						NORESIZE="NORESIZE" SCROLLING="AUTO" HEIGHT="400" WIDTH="600" BORDER="0"
						FRAMEBORDER="0"></IFRAME></TD> 
				  </TR> 
				</TABLE></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2">&nbsp;</TD> 
		  </TR> 
		  <TR> 
			 <TD><A NAME="dom">Page Output PHP Class Source</A>: </TD>
			 <TD ALIGN="RIGHT"><INPUT TYPE="BUTTON" VALUE="Close Window"
				ONCLICK="self.close()"></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="3">' . $this->vpi['outputPhpFile'] . '</TD>
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2"> 
				<TABLE BGCOLOR="#000000"> 
				  <TR> 
					 <TD BGCOLOR="#FFFFFF"><IFRAME SRC="dom.txt" NORESIZE="NORESIZE"
						SCROLLING="AUTO" HEIGHT="400" WIDTH="600" BORDER="0"
						FRAMEBORDER="0"></IFRAME></TD> 
				  </TR> 
				</TABLE></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2">&nbsp;</TD> 
		  </TR> 
		  <TR> 
			 <TD><A NAME="xml">Page Output Dynamic XML</A></TD>
			 <TD ALIGN="RIGHT"><INPUT TYPE="BUTTON" VALUE="Close Window"
				ONCLICK="self.close()"></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2"> 
				<TABLE BGCOLOR="#000000"> 
				  <TR> 
					 <TD BGCOLOR="#FFFFFF"><IFRAME SRC="xml.txt"
						NORESIZE="NORESIZE" SCROLLING="AUTO" HEIGHT="400" WIDTH="600" BORDER="0"
						FRAMEBORDER="0"></IFRAME></TD> 
				  </TR> 
				</TABLE></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2">&nbsp;</TD> 
		  </TR> 
		  <TR> 
			 <TD><A NAME="xsl">Page Output XSL Stylesheet</A>:</TD>
			 <TD ALIGN="RIGHT"><INPUT TYPE="BUTTON" VALUE="Close Window"
				ONCLICK="self.close()"></TD> 
		  </TR> 
		  <TR> 
			 <TD COLSPAN="3">' . $this->vpi['xslFile'] . '</TD>
		  </TR> 
		  <TR> 
			 <TD COLSPAN="2"> 
				<TABLE BGCOLOR="#000000"> 
				  <TR> 
					 <TD BGCOLOR="#FFFFFF"><IFRAME SRC="page.xsl"
						NORESIZE="NORESIZE" SCROLLING="AUTO" HEIGHT="400" WIDTH="600" BORDER="0"
						FRAMEBORDER="0"></IFRAME></TD> 
				  </TR> 
				</TABLE></TD> 
		  </TR> 
		</TABLE></FORM> </BODY>
</HTML>
');

      fflush($logHandle);
      fclose($logHandle);
   }
   /**#@-*/

   /**
    * redirect page to new URL
    *
    * @param string redirect URL
    * @param string is the redirect a dizzyPages URL
    * <ul>
    * <li> "Y" - Yes the URL is a dizzyPages URL
    * <li> "N" - No the URL is not a dizzyPages URL
    * </ul>
    * @param string is the redirect caused by a user requesting an
    * invalid navigation
    * <ul>
    * <li> "Y" - Yes the redirect was caused by an invalid navigation
    * <li> "N" - No the redirect was not caused by an invalid navigation
    * </ul>
    * @return boolean true - redirect performed
    * @throws -101, Redirect to [URL] failed the current page has already been redirected to [URL].
    */
   public function redirect($url, $isDzeUrl = "Y", $isInvalidNav = "N") {
      static $redirectUrl;

      if ($this->isRedirected != 'Y') {
         $this->isRedirected = 'Y';
         $redirectUrl = $url;

         header("HTTP/1.1 302 Moved Temporarily");
         $fullUrl =
            ($isDzeUrl == "Y")
               ?   "http://" . $_SERVER["HTTP_HOST"]
                 . (  $_SERVER["SERVER_PORT"] != "80"
                    ? (":" . $_SERVER["SERVER_PORT"])
                    : "")
                 . $this->config["indexPage"]
                 . "?" . "url=" . $url
                 . "&" . "redirect=Y"
                 . "&" . $this->sesName . "=" . $this->sesExtId
                 . "&pageid=" . $this->pageId
               : $url;
         header("Location: " . $fullUrl);

         if ($isInvalidNav == "Y") $this->setSesAttrib("common-SESSION-INVALID_NAV", "Y");
      }
      else throw new Exception("Redirect to '" . $url . "' failed the current page has already been redirected to '" . $redirectUrl . "'.", -101);

      return true;
   }

   /**
    * get session persistent attribute value
    *
    * @param string attribute name
    * @param integer optional record number of attribute to retrieve
    * @return string attribute value
    */
   public function getSesAttrib($name, $rec = 1) {
      return $this->getUserAttrib($name, $rec, $this->sesId);
   }

   /**
    * get user persistent attribute value
    *
    * @param string attribute name
    * @param integer optional record number of attribute to retrieve
    * @param integer optional user id.
    * If 0 the current logged in user's id is used.
    * @return string - attribute value
    * @throws -111, The session is not logged in.
    */
   public function getUserAttrib($name, $rec = 1, $userId = 0) {
      # TODO handle values larger than 255 characters
      if ($userId == 0) $userId = $this->getUserId();

      $this->sql->pushResult();

      $this->sql->select(
         "DZE_USR_ATTRIB",
         array("VALUE"),
         array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
            "REC_ID"      => $rec
         )
      );

      $row = $this->sql->fetchAssoc();

      $this->sql->freeResult();
      $this->sql->popResult();

      return $row["VALUE"];
   }

   /**
    * delete a session persistent attribute value
    *
    * @param string attribute name
    * @param integer optional record number of attribute to delete
    * @return boolean true - attribute value deleted
    */
   public function delSesAttrib($name, $rec = 1) {
      return $this->delUserAttrib($name, $rec, $this->sesId);
   }

   /**
    * delete a user persistent attribute value
    *
    * @param string attribute name
    * @param integer optional record number of attribute to delete
    * @param integer optional user id.
    * If 0 the current logged in user's id is used.
    * @return boolean true - attribute value deleted
    * @throws -111, The session is not logged in.
    */
   public function delUserAttrib($name, $rec = 1, $userId = 0) {
      if ($userId == 0) $userId = $this->getUserId();

      $this->sql->pushResult();

      $this->sql->delete(
         "DZE_USR_ATTRIB",
         array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
            "REC_ID"      => $rec
         )
      );

      $this->sql->delete(
         "DZE_USR_ATTRIB_ERR",
         array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
            "REC_ID"      => $rec
         )
      );

      $this->sql->query(
           "update DZE_USR_ATTRIB"
         . "   set REC_ID = REC_ID - 1"
         . " where USR_ID      = "  . $userId
         . "   and ATTRIB_NAME = '" . $name . "'"
         . "   and REC_ID      > "  . $rec
      );

      $this->sql->query(
           "update DZE_USR_ATTRIB_ERR"
         . "   set REC_ID = REC_ID - 1"
         . " where USR_ID      = "  . $userId
         . "   and ATTRIB_NAME = '" . $name . "'"
         . "   and REC_ID      > "  . $rec
      );

      $this->sql->popResult();

      return true;
   }

   /**
    * audit the deletion a session persistent attribute value
    *
    * @param string attribute name
    * @param integer optional record number of attribute to audit
    * @return boolean true - deletion of attribute value audited
    */
   public function auditDelSesAttrib($name, $rec = 1) {
      return $this->auditDelUserAttrib($name, $rec, $this->sesId);
   }

   /**
    * audit the deletion a user persistent attribute value
    *
    * @param string attribute name
    * @param integer optional record number of attribute to audit
    * @param integer optional user id.
    * If 0 the current logged in user's id is used.
    * @return boolean true - deletion of attribute value audited
    * @throws -111, The session is not logged in.
    */
   public function auditDelUserAttrib($name, $rec = 1, $userId = 0) {
      if ($userId == 0) $userId = $this->getUserId();

      $this->sql->pushResult();

      // negative record ids in the audit table indicate deleted record
      $this->sql->select(
         "DZE_USR_ATTRIB_ADT",
         array("min(REC_ID) REC_ID"),
         array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
         )
      );

      $row = $this->sql->fetchAssoc();

      $this->sql->freeResult();

      $delRecId =
         $row['REC_ID'] == 1 
            ? -1
            : $row['REC_ID'] - 1;

      $this->sql->update(
         "DZE_USR_ATTRIB_ADT",
         array("REC_ID"   => $delRecId),
         array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
            "REC_ID"      => $rec
         )
      );

      // time stamp deletion
      $this->sql->insert(
         "DZE_USR_ATTRIB_ADT",
         array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
            "REC_ID"      => $delRecId,
            "CREATE_TS"   => $this->pageTs,
            "VALUE"       => ""
         )
      );

      $this->sql->query(
           "update DZE_USR_ATTRIB_ADT"
         . "   set REC_ID = REC_ID - 1"
         . " where USR_ID      = "  . $userId
         . "   and ATTRIB_NAME = '" . $name . "'"
         . "   and REC_ID      > "  . $rec
      );

      $this->sql->popResult();

      return true;
   }

   /**
    * get the outsatnding error of a session persistent attribute value
    * entered in the last form processed
    *
    * @param string attribute name
    * @param integer optional record number of attribute error to retrieve
    * @return string error text
    */
   public function getSesAttribError($name, $rec = 1) {
      return $this->getUserAttribError($name, $rec, $this->sesId);
   }

   /**
    * get the outsatnding error of a user persistent attribute value
    * entered in the last form processed
    *
    * @param string attribute name
    * @param integer optional record number of attribute error to retrieve
    * @param integer optional user id.
    * If 0 the current logged in user's id is used.
    * @return string error text or null if no error exists for the attribute
    * @throws -111, The session is not logged in.
    */
   public function getUserAttribError($name, $rec = 1, $userId = 0) {
      if ($userId == 0) $userId = $this->getUserId();

      $this->sql->pushResult();

      $this->sql->select(
         "DZE_USR_ATTRIB_ERR",
         array("ERR_TXT"),
         array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
            "REC_ID"      => $rec
         )
      );

      $row = $this->sql->fetchAssoc();

      $this->sql->freeResult();
      $this->sql->popResult();

      return $row["ERR_TXT"];
   }

   /**
    * set the outsatnding error of a session persistent attribute value
    * entered in the last form processed
    *
    * @param string attribute name
    * @param string error text
    * @param integer optional record number of attribute error to set
    * @return boolean true - error set
    */
   public function setSesAttribError($name, $err_txt, $rec = 1) {
      return $this->setUserAttribError($name, $err_txt, $rec, $this->sesId);
   }

   /**
    * set the outsatnding error of a user persistent attribute value
    * entered in the last form processed
    *
    * @param string attribute name
    * @param string error text
    * @param integer optional record number of attribute error to set
    * @param integer optional user id.
    * If 0 the current logged in user's id is used.
    * @return boolean true - error set
    * @throws -111, The session is not logged in.
    */
   public function setUserAttribError($name, $errText, $rec = 1, $userId = 0) {
      if ($userId == 0) $userId = $this->getUserId();

      $this->sql->pushResult();

      $this->sql->select(
         "DZE_USR_ATTRIB_ERR",
         array("'X'"),
         array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
            "REC_ID"      => $rec
         ),
         "FOR UPDATE"
      );

      if ($this->sql->fetchArray())
         $this->sql->update("DZE_USR_ATTRIB_ERR",
            array(
               "ERR_TXT"     => $errText
            ),
            array(
               "USR_ID"      => $userId,
               "ATTRIB_NAME" => $name,
               "REC_ID"      => $rec
            )
         );
      else
         $this->sql->insert("DZE_USR_ATTRIB_ERR",
            array(
               "USR_ID"      => $userId,
               "ATTRIB_NAME" => $name,
               "REC_ID"      => $rec,
               "ERR_TXT"     => $errText
            )
         );

      $this->sql->popResult();

      return true;
   }

   /**
    * get the user id of the current logged in user
    *
    * @return integer - user id
    * @throws -111, The session is not logged in.
    */
   public function getUserId() {
      $retValue = $this->userId;
      
      if (is_null($retValue))
         throw new Exception("The session is not logged in.", -111);

      return $retValue;
   }

   /**
    * get the current session id
    *
    * @return integer session id
    */
   public function getSesId() {
      return $this->sesId;
   }

   /**
    * set session persistent attribute value
    *
    * @param string attribute name
    * @param string attribute value
    * @param integer optional record number of attribute to set
    * @return boolean true - attribute value set
    */
   public function setSesAttrib($name, $value, $rec = 1) {
      return $this->setUserAttrib($name, $value, $rec, $this->sesId);
   }

   /** or null if no error exists for the attribute
    * set user persistent attribute value
    *
    * @param string attribute name
    * @param string attribute value
    * @param integer optional record number of attribute to set
    * @param integer optional user id.
    * If 0 the current logged in user's id is used.
    * @return boolean - attribute value set
    * @throws -111, The session is not logged in.
    */
   public function setUserAttrib($name, $value, $rec = 1, $userId = 0) {
      # TODO handle values larger than 255 characters
      if ($userId == 0) $userId = $this->getUserId();

      $this->sql->pushResult();

      $this->sql->select(
         "DZE_USR_ATTRIB",
         array("'X'"),
         array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
            "REC_ID"      => $rec
         ),
         "FOR UPDATE"
      );

      if ($this->sql->fetchArray())
         $this->sql->update("DZE_USR_ATTRIB",
            array(
               "VALUE"       => (is_null($value) ? "" : $value),
               "SET_TS"      => $this->pageTs
            ),
            array(
               "USR_ID"      => $userId,
               "ATTRIB_NAME" => $name,
               "REC_ID"      => $rec
            )
         );
      else
         $this->sql->insert("DZE_USR_ATTRIB",
            array(
               "USR_ID"      => $userId,
               "ATTRIB_NAME" => $name,
               "REC_ID"      => $rec,
               "VALUE"       => (is_null($value) ? "" : $value),
               "SET_TS"      => $this->pageTs
            )
         );

      $this->sql->popResult();

      return true;
   }

   /**
    * audit session attribute value
    *
    * @param string attribute name
    * @param string attribute value
    * @param integer optional record number of attribute to audit
    * @return boolean true - attribute value audited
    */
   public function auditSesAttrib($name, $value, $rec = 1) {
      return $this->auditUserAttrib($name, $value, $rec, $this->sesId);
   }

   /**
    * audit user attribute value
    *
    * @param string attribute name
    * @param string attribute value
    * @param integer optional record number of attribute to audit
    * @param integer optional user id.
    * If 0 the current logged in user's id is used.
    * @return boolean true - attribute value audited
    * @throws -111, The session is not logged in.
    */
   public function auditUserAttrib($name, $value, $rec = 1, $userId = 0) {
      # TODO handle values larger than 255 characters
      if ($userId == 0) $userId = $this->getUserId();

      $this->sql->pushResult();

      $this->sql->select(
         "DZE_USR_ATTRIB_ADT",
         array("'X'"),
         array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
            "REC_ID"      => $rec,
            "CREATE_TS"   => $this->pageTs
         ),
         "FOR UPDATE"
      );

      if ($this->sql->fetchArray())
         // this should never happen on a default page
         // only on a developer enhanced page
         $this->sql->update("DZE_USR_ATTRIB_ADT",
            array(
               "VALUE"       => (is_null($value) ? "" : $value),
            ),
            array(
               "USR_ID"      => $userId,
               "ATTRIB_NAME" => $name,
               "REC_ID"      => $rec,
               "CREATE_TS"   => $this->pageTs
            )
         );
      else
         $this->sql->insert("DZE_USR_ATTRIB_ADT", array(
            "USR_ID"      => $userId,
            "ATTRIB_NAME" => $name,
            "REC_ID"      => $rec,
            "CREATE_TS"   => $this->pageTs,
            "VALUE"       => (is_null($value) ? "" : $value)
         ));

      $this->sql->popResult();

      return true;
   }

   /**
    * delete all attribute values of a form
    *
    * @param string form name
    * @param integer optional user or session id
    * If 0 the current session id is used.
    * @return boolean true - form cleared
    */
   public function clearForm($formName, $userId = 0) {
      $this->sql->pushResult();

      if ($userId == 0) $userId = $this->sesId;

      $this->sql->select(
         "DZE_ATTRIB_IN_FORM",
         array("ATTRIB_NAME"),
         array("FORM_NAME" => $formName)
      );

      while ($attrib = $this->sql->fetchAssoc()) {
         $this->sql->pushResult();

         $this->sql->delete(
            "DZE_USR_ATTRIB",
            array(
               "USR_ID"      => $userId,
               "ATTRIB_NAME" => $attrib["ATTRIB_NAME"]
            ),
            "and REC_ID > 0"
         );

         $this->sql->popResult();
      }

      $this->setUserAttrib("__CT__" . $formName, "0", 1, $userId);

      $this->sql->freeResult();
      $this->sql->popResult();

      return true;
   }

   /**
    * clear all outstanding form errors
    *
    * @param integer optional user or session id
    * If 0 the current session id is used.
    * @return boolean true - errors cleared
    */
   public function clearErrors($userId = 0) {
      $this->sql->pushResult();

      if ($userId == 0) $userId = $this->sesId;

      $this->sql->delete(
         "DZE_USR_ATTRIB_ERR",
         array("USR_ID" => $userId)
      );

      $this->sql->delete(
         "DZE_USR_FORM_ERR",
         array("USR_ID" => $userId)
      );
      $this->setUserAttrib("common-SESSION-PREVIOUS-FORM_ERR", "N", 1, $userId);

      $this->sql->popResult();

      return true;
   }

   /**
    * set the logged in user for the current session
    *
    * @param string user id
    * @param string user name
    * @return boolean true - user set
    * @throws -112, Session already has an assigned user id.
    */
   public function setSesUser($userId, $userName) {
      if (! is_null($this->userId))
         throw new Exception("Session already has an assigned user id.", -112);

      $this->sql->pushResult();

      $this->userId = $userId;
      $this->sql->update(
         "DZE_SESSION",
         array("REGISTER_USR_ID" => $userId),
         array("SESSION_ID"  => $this->sesExtId)
      );
      $this->userName = $userName;
      $this->setSesAttrib('common-SESSION-USER-NAME', $userName);
      $this->loginApp = $this->getAppFromUrl($this->previousUrl);
      $this->setSesAttrib('common-SESSION-APP-NAME', $this->loginApp);

      $this->copyUserErrorToSession();

      $this->sql->popResult();

      return true;
   }
}
?>
Return current item: dizzyPages