<?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;
}
}
?>