Location: PHPKode > projects > dizzyPages > dizzypages/include/DzeDomDoc.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>
 */
/**
 * DzeDomDoc
 *
 * Document DOM contains the dynamic data of a dizzyPage
 *
 * @author Doron Enav
 */
class DzeDomDoc
{
   private $queryDom;

   /**
    * @var object DzePage handle to current dizzyPage
    */
   protected $page;
   /**
    * @var object DzeMySQL handle to current sql transaction
    */
   protected $sql;
   /**
    * @var object DomDocument XML DOM Document handle for the output dizzyPage returned by the PHP {@link http://www.php.net/manual/en/ref.domxml.php DOM XML} library {@link http://www.php.net/manual/en/function.domxml-new-doc.php domxml_new_doc("1.0")} method
    */
   protected $xmlDoc;
   /**
    * @var object DomNode root node for the output dizzyPage DOM {@link $xmlDoc}
    */
   protected $rootNode;

   private function getDomPage($parentNode, $url)
   { 
      $this->sql->pushResult();

      # TODO What if page is not found in DB
      $this->sql->select(
         "DZE_PAGE",
         array("TITLE"),
         array("PAGE_URL" => $url)
      );

      $row = $this->sql->fetchAssoc();
      $this->appendCdataNode($parentNode, "title", $row["TITLE"]);

      $urlNode = $this->appendCdataNode($parentNode, "url", $url);
      $urlNode->setAttribute(
         "is_public_b",
         $this->page->getMember("urlIsPublic") == 'Y' ? 'Y' : 'N'
      );
      $urlNode->setAttribute("exists_b", $row ? 'Y' : 'N');

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

      while ($row = $this->sql->fetchAssoc()) {
         $formNode = $this->appendNode($parentNode, "form");
         $formNode->setAttribute("name", $row["FORM_NAME"]);

         $this->getDomForm($row["FORM_NAME"], $formNode);
      }

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

   private function getDomFormAttrib($formName, $formNode, $formType)
   {
      $this->sql->pushResult();

      $recCt = $this->page->getSesAttrib("__CT__" . $formName);

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

      while ($row = $this->sql->fetchAssoc()) {
         $attribNode = $this->appendNode($formNode, "attrib");
         $attribNode->setAttribute("name", $row["ATTRIB_NAME"]);

         $this->getDomAttrib($row["ATTRIB_NAME"], $attribNode,
            (is_null($recCt)
               ? ($formType == "S" ? 1 : 0)
               : $recCt
            )
         );
      }

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

   private function getDomForm($formName, $formNode)
   { 
      $this->sql->pushResult();

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

      $row = $this->sql->fetchAssoc();
      $formNode->setAttribute("type", $row["TYPE"]);
      $formNode->setAttribute("method", $row["METHOD"]);
      $formNode->setAttribute("trap_on_err_type", $row["TRAP_ON_ERR_TYPE"]);
      $formNode->setAttribute("display_only_b", "N");
      $formNode->setAttribute("skip_b", "N");
      $this->appendCdataNode($formNode, "title", $row["TITLE"]);
      $this->appendCdataNode($formNode, "action_url", $row["ACTION_URL"]);

      $this->getDomFormAttrib($formName, $formNode, $row["TYPE"]);

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

   private function getDomAttrib($name, $attribNode, $recLast)
   { 
      $this->sql->pushResult();

      # TODO handle default_value greater than 255 characters
      $this->sql->select(
         "DZE_ATTRIB",
         array(
            "TYPE",
            "DFLT_VALUE",
            "SAVE_TYPE",
            "REQUIRED_B",
            "S_SLCT_DYN_B",
            "S_FORM_TYPE",
            "S_FORM_COLS",
            "S_FORM_ORDER_TYPE",
            "FORM_LABEL",
            "T_FORM_TYPE",
            "T_FORM_LEN_MAX",
            "T_FORM_LEN",
            "T_AREA_FORM_ROWS",
            "M_SLCT_OPTION_MIN",
            "M_SLCT_OPTION_MAX"
         ),
         array("ATTRIB_NAME" => $name)
      );
      $row = $this->sql->fetchAssoc();

      for ($recCt = 1; $recCt <= $recLast; $recCt++) {
         $recNode = $this->appendNode($attribNode, "record");
         switch ($row["SAVE_TYPE"]) {
            case "S":
               $usr_valueS = $this->page->getSesAttrib($name, $recCt);
               break;
            case "U":
               $usr_valueS = $this->page->getUserAttrib($name, $recCt);
               break;
            default:
               $usr_valueS = null;
         }

         if (! is_null($usr_valueS)) {
            switch ($row["TYPE"]) {
               case "M":
                  $tmp_valueS = explode("|", $usr_valueS);
                  foreach($tmp_valueS as $usr_value)
                     $this->appendCdataNode($recNode, "value", $usr_value);
                  break;
               case "P":
                  // Password attrib should not return its value
                  $this->appendCdataNode($recNode, "value", "");
                  break;
               default:
                  $this->appendCdataNode($recNode, "value", $usr_valueS);
            }
         }

         if ($error = $this->page->getSesAttribError($name, $recCt)) {
            $errNode = $this->appendCdataNode($recNode, "err_txt", $error);
            $errNode->setAttribute("id", ++$this->errCt);
         }

      }

      $attribNode->setAttribute("type", $row["TYPE"]);
      $attribNode->setAttribute("required_b", $row["REQUIRED_B"]);
      $attribNode->setAttribute("display_only_b", "N");
      $attribNode->setAttribute("skip_b", "N");

      if (!is_null($row["FORM_LABEL"])) $this->appendCdataNode($attribNode, "form_label", $row["FORM_LABEL"]);

      if ($row["TYPE"] == "S" || $row["TYPE"] == "M") {
         $attribNode->setAttribute("dyn_b", $row["S_SLCT_DYN_B"]);
         $attribNode->setAttribute("select_type", $row["S_FORM_TYPE"]);
         if ($row["S_FORM_TYPE"] == 'B') {
            $attribNode->setAttribute("select_order_type", $row["S_FORM_ORDER_TYPE"]);
            IF (!is_null($row["S_FORM_COLS"]))
               $attribNode->setAttribute("select_cols", $row["S_FORM_COLS"]);
         }
         if (!is_null($row["M_SLCT_OPTION_MIN"]))
            $attribNode->setAttribute("option_min", $row["M_SLCT_OPTION_MIN"]);
         if (!is_null($row["M_SLCT_OPTION_MAX"]))
            $attribNode->setAttribute("option_max", $row["M_SLCT_OPTION_MAX"]);

         $this->getDomAttribSlct($name, $attribNode);
      }
      else {
         if (!is_null($row["DFLT_VALUE"]))
            $this->appendCdataNode($attribNode, "dflt_value", $row["DFLT_VALUE"]);
         if (!is_null($row["T_FORM_LEN_MAX"]))
            $attribNode->setAttribute("text_len_max", $row["T_FORM_LEN_MAX"]);
         if (!is_null($row["T_FORM_LEN"]))
            $attribNode->setAttribute("text_box_len", $row["T_FORM_LEN"]);
         if ($row["TYPE"] == "T") {
            $attribNode->setAttribute("text_type", $row["T_FORM_TYPE"]);
            if ($row["T_FORM_TYPE"] == "A")
               $attribNode->setAttribute("textarea_rows", $row["T_AREA_FORM_ROWS"]);
         }
      }

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

   private function getDomAttribSlct($name, $attribNode)
   { 
      $this->sql->pushResult();

      # TODO handle default_value greater than 255 characters
      $this->sql->select(
         "DZE_SLCT_OPTION",
         array(
            "VALUE",
            "FORM_LABEL",
            "DEFAULT_B"
         ),
         array("ATTRIB_NAME" => $name),
         "ORDER BY DISPLAY_ORDER"
      );

      while ($row = $this->sql->fetchAssoc()) {
         $attribSlctNode = $this->appendNode($attribNode, "option");
         $attribSlctNode->setAttribute("value", $row["VALUE"]);
         $attribSlctNode->setAttribute("default_b", $row["DEFAULT_B"]);
         $this->appendCdataNode($attribSlctNode, "form_label", $row["FORM_LABEL"]);
      }

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

   private function root_node($name) 
   {
      return $this->xmlDoc->appendChild($this->xmlDoc->createElement($name));
   }

   /**
    * Create an instance of a dizzyPages DOM document.
    *
    * This is the container for all dynamic content presented to a user on
    * a page.
    *
    * @param object DzePage handle to the current DzePage instance
    */
   public function __construct(& $dzePage)
   {
      $this->page = & $dzePage;
      $this->sql = & $this->page->sql;

      $this->errCt = 0;
   }

   /**
    * Build the XML DOM containing all the dynamic data for the page.
    *
    * Builds XML DOM containing dynamic data required to display a
    * page, the forms belonging to the page, the attributes belonging to the
    * forms, outstanding attribute errors, session data saved under the
    * attributes, and user data saved under the attributes.
    *
    * @return boolean true - built XML DOM
    */
   protected function buildXmlDom()
   {
      $this->xmlDoc = new DOMDocument();
      $this->rootNode = $this->root_node("dze");
      $idNode = $this->appendNode($this->rootNode, "id", $this->page->getMember("sesExtId"));
      $idNode->setAttribute("name", $this->page->getMember("sesName"));
      $this->appendNode($this->rootNode, "valid_navigation", (($this->page->isValidNavigation()) ? 'Y' : 'N'));
      $this->appendNode($this->rootNode, "page_id", $this->page->getMember("pageId"));
      $this->appendCdataNode($this->rootNode, "index_page", $this->page->getMember("config['indexPage']"));
      if (!is_null($this->page->getMember("loginApp")))
         $this->appendCdataNode($this->rootNode, "login_app", $this->page->getMember("loginApp"));
      if (!is_null($this->page->getMember("urlApp")))
         $this->appendCdataNode($this->rootNode, "url_app", $this->page->getMember("urlApp"));
      if (!is_null($this->page->getMember("urlAppTitle")))
         $this->appendCdataNode($this->rootNode, "url_app_title", $this->page->getMember("urlAppTitle"));
      if (!is_null($this->page->getMember("userName")))
         $this->appendCdataNode($this->rootNode, "user", $this->page->getMember("userName"));
      $this->getDomPage($this->rootNode, $this->page->getMember("url"));

      $this->queryDom = new DOMXPath($this->xmlDoc);

      return true;
   }

   /**
    * Transform {@link $xmlDoc} DOM Document into HTML using a static XSL template.
    *
    * The XSL Transform template file used is chosen in the following priority order:
    * <ol>
    * <li> <b>$xslFile</b> if supplied
    * <li> [dizzyPages root directory]/pageoutput/[application name]/[page directory]/[page base name].xsl
    * <li> [dizzyPages root directory]/pageoutput/[application name]/[page directory]/default.xsl
    * <li> [dizzyPages root directory]/pageoutput/[application name]/default.xsl
    * <li> [dizzyPages root directory]/pageoutput/default.xsl
    * </ol>
    *
    * If <b>$xslFile</b> is supplied and is not found an exception is thrown.  In all other cases the first found template is used to perform the transformation.
    *
    * Use [dizzyPages root directory]/pageoutput/default.xsl as an example or starting point for writing custom templates.  Remember to adjust the 'xsl:include' directives in your new custom xsl template to reflect its new location.
    *
    * @param string optional XSL file used to perform transformation
    * @return boolean 
    * <ul>
    * <li> true - transform performed
    * <li> false - transform not performed due to user page abort
    * </ul>
    * @throws -301, File [XSL File] does not exist or is not readable.
    */
   protected function xslTransform($xslFile = null) {
      $retValue = true;

      if (!is_null($xslFile) && !file_exists($xslFile))
         throw new Exception("File '" . $xslFile . "' does not exist or is not readable.", -301);
      else {
         $xslFile = $this->page->getMember('config["directory"]') . "/pageoutput" . $this->page->getMember("urlBase") . ".xsl";
         if (!file_exists($xslFile)) {
            $xslFile = $this->page->getMember('config["directory"]') . "/pageoutput" . dirname($this->page->getMember("urlBase")) . "/default.xsl";
            if (!file_exists($xslFile)) {
               $xslFile = $this->page->getMember('config["directory"]') . "/pageoutput/" . $this->page->getMember("urlApp") . "/default.xsl";
               if (   !file_exists($xslFile)
                   && $this->page->getMember("loginApp") == 'dze'
                   && $this->page->getMember("urlApp") != 'dze')
                  $xslFile = $this->page->getMember('config["directory"]') . "/pageoutput/dze/default.xsl";
               else
                  $xslFile = $this->page->getMember('config["directory"]') . "/pageoutput/default.xsl";
            }
         }
      }

      if (   $this->page->getMember("loginApp") == 'dze'
          || $this->page->getMember("config['log_level']") >= 5) {
         $this->page->setVpi("xslFile", $xslFile);
         $this->xmlDoc->formatOutput = true;
         $this->page->setVpi("xml", $this->xmlDoc->saveXML());
         $this->xmlDoc->formatOutput = false;
      }

      // There are two possible XSL Transform libraries that may be installed
      // Gnome libxslt or Sablotron
      // Since we are already using Gnome libxml for the DOM object I chose
      // to look for Gnome libxslt first.  It also will take DOM XML as
      // input where Sablotron requires text XML

      // don't perform transform if the user has disconnected
      if (! connection_aborted())
         if (class_exists("xsltProcessor")) {
            $xslt = new xsltProcessor;
            $xslt->importStyleSheet(DomDocument::load($xslFile));
            print $xslt->transformToXML($this->xmlDoc);
         }
         else {
            $arguments = array('/_xml' => $this->xmlDoc->saveXML());
            $xh = xslt_create();
            echo(xslt_process($xh, 'arg:/_xml', $xslFile, NULL, $arguments));
         }
      else $retValue = false;

      return $retValue;
   }

   /**
    * Append a child node with cdata content to an existing node
    *
    * @param object DomNode parent node of new node
    * @param string tag name of new node
    * @param string cdata content of new node
    * @return object DomNode new node
    */
   protected function appendCdataNode($node, $name, $content) 
   {
      $tmpNode = $node->appendChild($this->xmlDoc->createElement($name));
      $tmpNode->appendChild($this->xmlDoc->createCDATASection($content));
      return $tmpNode;
   }

   /**
    * Append a child node with optional content to an existing node
    *
    * @param object DomNode parent node of new node
    * @param string tag name of new node
    * @param string content of new node
    * @return object DomNode new node
    */
   protected function appendNode($node, $name, $content = null) 
   {
      $tmpNode = $node->appendChild($this->xmlDoc->createElement($name));
      if (! is_null($content))
         $tmpNode->appendChild($this->xmlDoc->createTextNode($content));
      return $tmpNode;
   }

   /**
    * Sets a DOM attribute for a form/s and/or each of its attributes.
    * 
    * @param mixed string containing a form name
    * or an array of strings containing form names
    * @param string DOM Attribute name
    * @param string DOM Attribute value
    * @param string set DOM attribute:
    * <ul>
    * <li> 'F' - <b>$formName</b> forms only
    * <li> 'A' - <b>$formName</b> form attributes only
    * <li> 'B' - <b>$formName</b> form and form attributes
    * </ul>
    * @return boolean true - DOM Attribute set
    * @throws -311, Form node for form [Form Name] was not found
    * @throws -351, Invalid <b>setType</b> parameter value
    */
   protected function setFormNodeDOMAttrib(
      $formNameS, $domAttrib, $domValue, $setType = "F"
   ) {
      if ($setType != "F" && $setType != "A" && $setType != "B")
         throw new Exception("Invalid setType parameter value '$setType'.", -351);

      if (! is_array($formNameS)) $formNameS = array($formNameS);
      
      $formNodeS = array();
      foreach ($formNameS as $formName) {
         $formNode = $this->queryNode(
            "/dze/form[@name = '$formName']", null, false
         );
         if ($formNode->length != 1) {
            throw new Exception("Form node for form '$formName' was not found.", -311);
         }
         else array_push($formNodeS, $formNode->item(0));
      }

      foreach ($formNodeS as $formNode) {
         if ($setType == "F" || $setType == "B")
            $formNode->setAttribute($domAttrib, $domValue);
         if ($setType == "A" || $setType == "B") {
            $attribNodeS = $formNode->getElementsByTagname("attrib");
            for ($i = 0; $i < $attribNodeS->length; $i++)
               $attribNodeS->item($i)->setAttribute($domAttrib, $domValue);
         }
      }

      return true;
   }

   /**
    * Sets a DOM attribute for a form's attribute/s.
    * 
    * @param mixed string containing a form attribute name
    * or an array of strings containing form attribute names
    * @param string form name
    * @param string DOM attribute name
    * @param string DOM attribute value
    * @return boolean true - DOM Attribute set
    * @throws -311, Form node for form [Form Name] was not found
    * @throws -312, Attribute node for attribute [Attribute Name] in form [Form Name] was not found.
    */
   protected function setAttribNodeDOMAttrib(
      $attribNameS, $formName, $domAttrib, $domValue
   ) {
      if (! is_array($attribNameS)) $attribNameS = array($attribNameS);

      $formNode = $this->queryNode(
         "/dze/form[@name = '$formName']" , null, false
      );
      if ($formNode->length != 1)
         throw new Exception("Form node for form '$formName' was not found.", -311);

      $attribNodeS = array();
      foreach ($attribNameS as $attribName) {
         $attribNode = $this->queryNode(
            "attrib[@name = '$attribName']" , $formNode->item(0), false
         );
         if ($attribNode->length != 1)
            throw new Exception("Attribute node for attribute '$attribName' in form '$formName' was not found.", -312);
         else array_push($attribNodeS, $attribNode->item(0));
      }

      foreach ($attribNodeS as $attribNode)
         $attribNode->setAttribute($domAttrib, $domValue);

      return true;
   }

   /**
    * Remove form attribute/s from DOM.
    * 
    * @param mixed string containing a form attribute name
    * or an array of strings containing form attribute names
    * @param string form name
    * @return boolean true - Form attribute/s removed from DOM
    * @throws -311, Form node for form [Form Name] was not found
    * @throws -312, Attribute node for attribute [Attribute Name] in form [Form Name] was not found.
    */
   protected function removeAttribNode($attribNameS, $formName) {
      // INFO $attribNode->setAttribute("skip_b", "Y"); setting skip will have
      // similer effect at the XSL logic
      if (! is_array($attribNameS)) $attribNameS = array($attribNameS);

      $formNode = $this->queryNode(
         "/dze/form[@name = '$formName']", null, false
      );
      if ($formNode->length != 1)
         throw new Exception("Form node for form '$formName' was not found.", -311);

      $attribNodeS = array();
      foreach ($attribNameS as $attribName) {
         $attribNode = $this->queryNode(
            "attrib[@name = '$attribName']", $formNode->item(0), false
         );
         if ($attribNode->length != 1)
            throw new Exception("Attribute node for attribute '$attribName' in form '$formName' was not found.", -312);
         else array_push($attribNodeS, $attribNode->item(0));
      }

      foreach ($attribNodeS as $attribNode)
         $formNode->item(0)->removeChild($attribNode);

      return true;
   }

   /**
    * Remove form/s from DOM.
    * 
    * @param mixed string containing a form name
    * or an array of strings containing form names
    * @return boolean true - Form/s removed from DOM
    * @throws -311, Form node for form [Form Name] was not found
    */
   protected function removeFormNode($formNameS) {
      if (! is_array($formNameS)) $formNameS = array($formNameS);

      $formNodeS = array();
      foreach ($formNameS as $formName) {
         $formNode = $this->queryNode(
            "/dze/form[@name = '$formName']", null, false
         );
         if ($formNode->length != 1)
            throw new Exception("Form node for form '$formName' was not found.", -311);
         else array_push($formNodeS, $formNode->item(0));
      }

      foreach ($formNodeS as $formNode)
         $this->rootNode->removeChild($formNode);

      return true;
   }

   /**
    * Query DOM for a specific DOMNode or a DOMNodeList
    * 
    * @param string XPATH query
    * @param DOMNode an optional starting point DOMNode for the search to start
    * from.  The default search starts from the root DOMNode.
    * @param boolean if true return the first DOMNode in the result of the
    * query.  This is usefull to most queries that expect only one DOMNode in
    * the result set.
    * @return mixed
    * <ul>
    * <li> DOMNode - the first DOMNode in the query result set
    * <li> DOMNodeList - the DOMNodeList of the query result set (returned
    * when getFirstItem is set to false)
    * </ul>
    * @throws -321, Query [Query String] returned an empty DOMNodeList; there is no first item to return.
    */
   protected function queryNode($queryString, $fromNode = null, $getFirstItem = true) {
      if (is_null($fromNode))
         $nodeList = $this->queryDom->query($queryString);
      else
         $nodeList = $this->queryDom->query($queryString, $fromNode);

      if ($getFirstItem)
         if ($nodeList->length == 0)
            throw new Exception("Query '$queryString' returned an empty DOMNodeList; there is no first item to return.", -321);
         else return $nodeList->item(0);
      else
         return $nodeList;
   }
}
?>
Return current item: dizzyPages