diff --git a/config/siteMap.inc b/config/siteMap.inc index c746681..1762189 100644 --- a/config/siteMap.inc +++ b/config/siteMap.inc @@ -138,6 +138,8 @@ $default->siteMap->addPage("docLinkManagement", "/presentation/lookAndFeel/knowl $default->siteMap->addPage("manageHelp", "/presentation/lookAndFeel/knowledgeTree/administration/help/manageHelp.php", "Administration", SysAdmin, _("Help Administration"), true, 17); $default->siteMap->addPage("managePermissions", "/presentation/lookAndFeel/knowledgeTree/administration/permissions/managePermissions.php", "Administration", SysAdmin, _("Permissions Administration"), true, 18); +$default->siteMap->addPage("manageFieldsets", "/presentation/lookAndFeel/knowledgeTree/administration/fieldsetmanagement/manageFieldsets.php", "Administration", SysAdmin, _("Fieldsets Administration"), true, 19); +$default->siteMap->addPage("manageLookupTrees", "/presentation/lookAndFeel/knowledgeTree/administration/docfieldmanagement/manageLookupTrees.php", "Administration", SysAdmin, _("Lookup Tree Administration"), true, 20); $default->siteMap->addSectionColour("Administration", "th", "056DCE"); $default->siteMap->addSectionColour("Administration", "td", "6699FF"); diff --git a/config/tableMappings.inc b/config/tableMappings.inc index 06d19b3..422ed33 100644 --- a/config/tableMappings.inc +++ b/config/tableMappings.inc @@ -28,6 +28,8 @@ $default->sessions_table = "active_sessions"; $default->data_types_table ="data_types"; // document type fields $default->document_fields_table = "document_fields"; +// document type fields +$default->document_fieldset_table = "document_fieldsets"; // links document $default->document_fields_link_table = "document_fields_link"; // document subscriptions @@ -38,6 +40,8 @@ $default->transaction_types_table = "document_transaction_types_lookup"; $default->document_transactions_table = "document_transactions"; // links document types to document type fields $default->document_type_fields_table = "document_type_fields_link"; +// links document types to document type fields +$default->document_type_fieldsets_table = "document_type_fieldsets_link"; // document type information $default->document_types_table = "document_types_lookup"; // stores documents @@ -122,4 +126,8 @@ $default->permission_descriptor_groups_table = "permission_descriptor_groups"; $default->permission_lookups_table = "permission_lookups"; $default->permission_lookup_assignments_table = "permission_lookup_assignments"; $default->groups_groups_table = "groups_groups_link"; +$default->metadata_treenode_table = "metadata_lookup_tree"; +$default->metadata_condition_table = "metadata_lookup_condition"; +$default->md_condition_table = "metadata_lookup_condition"; +$default->md_condition_chain_table = "metadata_lookup_condition_chain"; ?> diff --git a/graphics/minus.png b/graphics/minus.png new file mode 100644 index 0000000..40e5244 --- /dev/null +++ b/graphics/minus.png diff --git a/graphics/plus.png b/graphics/plus.png new file mode 100644 index 0000000..4c24f84 --- /dev/null +++ b/graphics/plus.png diff --git a/lib/documentmanagement/DocumentField.inc b/lib/documentmanagement/DocumentField.inc index 7c851b3..24cf027 100644 --- a/lib/documentmanagement/DocumentField.inc +++ b/lib/documentmanagement/DocumentField.inc @@ -28,125 +28,39 @@ require_once("$default->fileSystemRoot/lib/documentmanagement/DocumentType.inc") class DocumentField extends KTEntity { /** primary key value */ - var $iId; - /** document field name */ + var $iId = -1; var $sName; - /** document field data type */ var $sDataType; - /** is_generic */ var $bIsGeneric; - /**has lookup*/ var $bHasLookup; - - /** - * Default constructor - * - * @param Name of document field - * @param Document field data type - * - */ - function DocumentField($sNewName, $sNewDataType, $bNewIsGeneric, $bNewHasLookup) { - //object not created yet + var $iParentFieldset; + var $bHasLookupTree; + + // FIXME deprecate constructor use. Use DocumentField::createFromArray instead. + function DocumentField($sNewName = null, $sNewDataType = null, $bNewIsGeneric = null, $bNewHasLookup = null, $iParentFieldset = null, $bHasLookupTree = null) { + $this->iId = -1; $this->sName = $sNewName; $this->sDataType = $sNewDataType; $this->bIsGeneric = $bNewIsGeneric; $this->bHasLookup = $bNewHasLookup; - - } - - /** - * Get the document field's primary key value - * - * @return int document field's primary key value - * - */ - function getID() { - return $this->iId; + $this->iParentFieldset = $iParentFieldset; + $this->bHasLookupTree = $bHasLookupTree; } - /** - * Get the document field's name - * - * @return String document field's name - * - */ - function getName() { - return $this->sName; - } - - /** - * Set the document field's name - * - * @param Document field's new name - * - */ - function setName($sNewValue) { - $this->sName = $sNewValue; - } - - /** - * Get the document field's data type - * - * @return String document field's data type - * - */ - function getDataType() { - return $this->sDataType; - } - - /** - * Set the document field's data type - * - * @param Document field's new data type - * - */ - function setDataType($sNewValue) { - $this->sDataType = $sNewValue; - } - - - /** - * Get the document field's generic or not - * - * @return String document field's data type - * - */ - function getIsGeneric() { - return $this->bIsGeneric; - } - - /** - * Set the document field's genericness - * - * @param Document field's new data type - * - */ - function setIsGeneric($sNewValue) { - $this->bIsGeneric = $sNewValue; - } - - - - /** - * Get the document field's lookup or not - * - * @return String document field's data type - * - */ - function getHasLookup() { - return $this->bHasLookup; - } - - /** - * Set the document field's lookup - * - * @param Document field's new data type - * - */ - function setHasLookup($sNewValue) { - $this->bHasLookup = $sNewValue; - } + function getID() { return $this->iId; } + function getName() { return $this->sName; } + function setName($sNewValue) { $this->sName = $sNewValue; } + function getDataType() { return $this->sDataType; } + function setDataType($sNewValue) { $this->sDataType = $sNewValue; } + function getIsGeneric() { return $this->bIsGeneric; } + function setIsGeneric($sNewValue) { $this->bIsGeneric = $sNewValue; } + function getHasLookup() { return $this->bHasLookup; } + function setHasLookup($iNewValue) { $this->bHasLookup = $iNewValue; } + function getParentFieldset() { return $this->iParentFieldset; } + function setParentFieldset($iNewValue) { $this->iParentFieldset = $iNewValue; } + function getHasLookupTree() { return $this->bHasLookupTree; } + function setHasLookupTree($iNewValue) { $this->bHasLookupTree = KTUtil::anyToBool($iNewValue); } function _fieldValues () { return array( @@ -154,6 +68,8 @@ class DocumentField extends KTEntity { 'data_type' => $this->sDataType, 'is_generic' => KTUtil::anyToBool($this->bIsGeneric), 'has_lookup' => KTUtil::anyToBool($this->bHasLookup), + 'parent_fieldset' => $this->iParentFieldset, + 'has_lookuptree' => KTUtil::anyToBool($this->bHasLookupTree), ); } @@ -161,7 +77,16 @@ class DocumentField extends KTEntity { global $default; return $default->document_fields_table; } + + function &getList($sWhereClause = null) { + return KTEntityUtil::getList2('DocumentField', $sWhereClause); + } + + function &createFromArray($aOptions) { + return KTEntityUtil::createFromArray('DocumentField', $aOptions); + } + // FIXME remove &get and rather use the autogenerated version. /** * Static function. * Given a document_fields primary key it will create a @@ -178,6 +103,8 @@ class DocumentField extends KTEntity { if ($sql->next_record()) { $oDocumentField = & new DocumentField($sql->f("name"), $sql->f("data_type"), (bool)$sql->f("is_generic"), (bool)$sql->f("has_lookup")); $oDocumentField->iId = $sql->f("id"); + $oDocumentField->iParentFieldset = $sql->f("parent_fieldset"); + $oDocumentField->bHasLookupTree = (bool)$sql->f("has_lookuptree"); return $oDocumentField; } $_SESSION["errorMessage"] = $lang_err_object_not_exist . "id = " . $iDocumentID . " table = $default->document_fields_table"; @@ -187,16 +114,6 @@ class DocumentField extends KTEntity { return false; } - /** - * Static- Get a list of document fieldss; - * - * @param String Where clause (not required) - * - * @return Array array of DocumentField objects, false otherwise - */ - function getList($sWhereClause = null) { - return KTEntityUtil::getList(DocumentField::_table(), 'DocumentField', $sWhereClause); - } /** * Returns the DocumentTypes mapped to this document field diff --git a/lib/documentmanagement/DocumentFieldSet.inc b/lib/documentmanagement/DocumentFieldSet.inc new file mode 100644 index 0000000..a25b365 --- /dev/null +++ b/lib/documentmanagement/DocumentFieldSet.inc @@ -0,0 +1,79 @@ +fileSystemRoot/lib/documentmanagement/DocumentType.inc"); +require_once(KT_LIB_DIR . "/ktentity.inc"); + +/** + * class DocumentFieldSet + * + * Represents the basic grouping of fields into a fieldset. + */ +class DocumentFieldSet extends KTEntity { + + /** primary key value */ + var $iId = -1; + /** document fieldset name */ + var $sName; + /** document fieldset namespace */ + var $sName; + /** document fieldset mandatory flag*/ + var $bMandatory; + var $bIsConditional; + var $iMasterField; + + var $_bUsePearError = true; + + function getId() { return $this->iId; } + function getName() { return $this->sName; } + + function setName($sNewValue) { $this->sName = $sNewValue; } + function getNamespace() { return $this->sNamespace; } + function setNamespace($sNewcreValue) { $this->sNamespace = $sNewValue; } + function getMandatory() { return $this->bMandatory; } + function setMandatory($bNewValue) { $this->bMandatory = $bNewValue; } + function getIsConditional () { return $this->bIsConditional; } + function setIsConditional ($bNewValue) { $this->bIsConditional = $bNewValue; } + function getMasterField () { return $this->bIsConditional; } + function setMasterField ($iNewValue) { $this->bIsConditional = $iNewValue; } + + var $_aFieldToSelect = array( + "iId" => "id", + "sName" => "name", + "sNamespace" => "namespace", + "bMandatory" => "mandatory", + "bIsConditional" => "is_conditional", + "iMasterField" => "master_field", + ); + + // returns TRUE if all children are lookup enabled, false otherwise. + function canBeMadeConditional() { + if ($this->getIsConditional()) { + return false; + } + + // DEBUG + return false; + } + + function _fieldValues () { + return array( + 'name' => $this->sName, + 'namespace' => $this->sNamespace, + 'mandatory' => $this->bMandatory, + 'is_conditional' => KTUtil::anyToBool($this->bMandatory), + 'master_field' => $this->iMasterField, + ); + } + + function _table () { + global $default; + return $default->document_fieldset_table; + } + + // Static function + function &get($iId) { return KTEntityUtil::get('DocumentFieldSet', $iId); } + function &getList($sWhereClause = null) { return KTEntityUtil::getList2('DocumentFieldSet', $sWhereClause); } + function &createFromArray($aOptions) { return KTEntityUtil::createFromArray('DocumentFieldSet', $aOptions); } +} + +?> diff --git a/lib/documentmanagement/MDCondition.inc b/lib/documentmanagement/MDCondition.inc new file mode 100644 index 0000000..227e45c --- /dev/null +++ b/lib/documentmanagement/MDCondition.inc @@ -0,0 +1,80 @@ + "id", + "iFieldId" => "document_field_id", + "iLookupId" => "metadata_lookup_id", + "sName" => "name", // this can be null - if it isn't we are looking at a rooted subtree + ); + + var $_bUsePearError = true; + + function getID() { return $this->iId; } + function setID($iId) { $this->iId = $iId; } + function getFieldId() { return $this->iFieldId; } + function setFieldId($iFieldId) { $this->iFieldId = $iFieldId; } + function getLookupId() { return $this->iLookupId; } + function setLookupId($iLookupId) { $this->$iLookupId = $iLookupId; } + + function _table () { + global $default; + return $default->metadata_condition_table; + } + + // Static Functions (dull) + function &get($iId) { return KTEntityUtil::get("MDConditionNode", $iId); } + function &createFromArray($aOptions) { return KTEntityUtil::createFromArray("MDConditionNode", $aOptions); } + function &getList($sWhereClause = null) { global $default; return KTEntityUtil::getList2("MDConditionNode", $sWhereClause); } + + /** end boilerplate. anything interesting goes below here. */ + +} + +class MDConditionChain extends KTEntity { + /** boilerplate DB code. */ + /** primary key */ + var $iId = -1; + var $iParentCondition; + var $iChildCondition; + + var $_aFieldToSelect = array( + "iId" => "id", + "iChildCondition" => "child_condition", + "iParentCondition" => "parent_condition", + ); + + var $_bUsePearError = true; + + function getID() { return $this->iId; } + function setID($iId) { $this->iId = $iId; } + function getParentConditionId() { return $this->iParentCondition; } + function setParentConditionId($iParentCondition) { $this->$iParentCondition = $iParentCondition; } + function getChildConditionId() { return $this->iChildCondition; } + function setChildConditionId($iChildCondition) { $this->$iChildCondition = $iParentCondition; } + + function _table () { + global $default; + return $default->md_condition_chain_table; + } + + // Static Functions (dull) + function &get($iId) { return KTEntityUtil::get("MDConditionChain", $iId); } + function &createFromArray($aOptions) { return KTEntityUtil::createFromArray("MDConditionChain", $aOptions); } + function &getList($sWhereClause = null) { global $default; return KTEntityUtil::getList2("MDConditionChain", $sWhereClause); } + + /** end boilerplate. anything interesting goes below here. */ + +} diff --git a/lib/documentmanagement/MDTree.inc b/lib/documentmanagement/MDTree.inc new file mode 100644 index 0000000..f77690d --- /dev/null +++ b/lib/documentmanagement/MDTree.inc @@ -0,0 +1,295 @@ + "id", + "iFieldId" => "document_field_id", + "sName" => "name", + "iParentNode" => "metadata_lookup_tree_parent", + ); + + var $_bUsePearError = true; + + function getID() { return $this->iId; } + function setID($iId) { $this->iId = $iId; } + function getFieldId() { return $this->iFieldId; } + function setFieldId($iFieldId) { $this->iFieldId = $iFieldId; } + function getName() { return $this ->sName; } + function setName($sName) { $this->sName = $sName; } + function getParentNode() { return $this->iParentNode; } + function setParentNode($iNode) { $this->iParentNode = $iParentNode; } + + function _table () { + global $default; + return $default->metadata_treenode_table; + } + + // Static Functions (dull) + function &get($iId) { return KTEntityUtil::get('MDTreeNode', $iId); } + function &createFromArray($aOptions) { return KTEntityUtil::createFromArray('MDTreeNode', $aOptions); } + function &getList($sWhereClause = null) { global $default; return KTEntityUtil::getList2('MDTreeNode', $sWhereClause); } + + /** end boilerplate. anything interesting goes below here. */ + +} + +/* simple class to encapsulate tree-as-a-whole behaviour. + NBM - should this move, be refactored? It certainly doesn't belong in the DB, + since its just an aggregate utility. +*/ +class MDTree { + var $contents = null; // private. + var $mapnodes = null; + var $root = null; + var $field_id; + var $lookups; + + function getRoot() { return $this->root; } + function getMapping() { return $this->mapnodes; } + function clear() { + $this->contents = null; + $this->mapnodes = null; + $this->root = null; + $this->lookups = null; + $this->field_id = null; + } + + /* function buildForField + * + * build a tree for a particular field instance. + * sets contents, so we can edit "stuff". + */ + function buildForField($iFieldId) + { + global $default; + // before we start, we need to check that + // the specified field exists and is organised into a tree. + $organisedField =& DocumentField::get($iFieldId); + if (PEAR::isError($organisedField) || ($organisedField === false)) { + $this->clear(); // make sure we don't get pollution. + return ; // and leave all null. WHY DOESN'T PHP HAVE EXCEPTIONS? + } + + if ($organisedField->getHasLookupTree() === false) { + $this->clear(); // make sure we don't get pollution. + return ; // not a tree-lookup. + } + // right. we are now ready to start with the treebuild. + // root is a virtual node (id: 0). + $this->field_id = $iFieldId; + $orderedTreeNodes =& MDTreeNode::getList('WHERE document_field_id = '.$iFieldId.' ORDER BY metadata_lookup_tree_parent, name ASC'); + + // since we have these nodes ordered by parent, we can perform a build + // we can build: + // $this->mapnodes [node_id => node] + // $this->root [node_id => subtree_root_arr] + // $this->contents [node_id => subtree_root_arr] + // THIS IS IMPORTANT: BOTH subtree_root_arr are the same object. + // Without this, we CAN'T build the tree this way. PLEASE, let PHP support + // this magic. + + // initialise. + + $this->mapnodes = array(); // will hold the actual nodes, mapped by id. + $this->contents = array(); // will hold references to each nodes subtree. + $this->contents[0] = array(); + + foreach ($orderedTreeNodes as $treeNode) { + + // step 1: set the map entry for this item. + $iParent = $treeNode->getParentNode(); + $iCurrId = $treeNode->getId(); + $this->mapnodes[$iCurrId] = $treeNode; // always works, setting our own value. + + $parent_arr = null; + if (!array_key_exists($iParent, $this->contents)) { $this->contents[$iParent] = array(); } + if (!array_key_exists($iCurr, $this->contents)) { $this->contents[$iCurrId] = array(); } + + $this->contents[$iParent][] = $iCurrId; + //$default->log->debug("MDTree::buildForField bound to subtree " . print_r($this->contents, true)); + } + + $md_list =& MetaData::getList('document_field_id = ' .$organisedField->getId()); + $this->lookups = array(); + foreach ($md_list as $lookup_value) { + + // failsafe. unparented and orphaned items go into root. + $iParentId = $lookup_value->getTreeParent(); + if ($iParentId === null) $iParentId = 0; + if (array_key_exists($iParentId, $this->contents)) { + $target_set =& $this->contents[$iParentId]; + } + else + { + $target_set =& $this->contents[0]; + } + + $leafArray = null; + if (!array_key_exists("leaves", $target_set)) { + $target_set["leaves"] = array($lookup_value->getId()); + } + else + { + + array_push($target_set["leaves"], $lookup_value->getId()); + } + + $this->lookups[$lookup_value->getId()] = $lookup_value; + + } + $this->root =& $this->contents[0]; + $default->log->debug("MDTree::buildForField done: " . print_r($this, true)); + + } + + // handle deleting subtrees + function deleteNode($iNode) { + $stack = array(); + array_push($stack, $iNode); + while (count($stack) != 0) + { + $currentNode = array_pop($stack); + foreach ($this->contents[$currentNode] as $label => $value) + { + if ($label === "leaves") + { + foreach ($value as $leaf) + { + $this->lookups[$leaf]->setTreeParent(0); + $this->lookups[$leaf]->update(); + $this->contents[0]["leaves"][] = $leaf; + } + } + else array_push($stack, $value); + } + $this->mapnodes[$currentNode]->delete(); + } + // finally, we prune the appropriate item from its parent. + $iParent = $this->mapnodes[$iNode]->getParentNode(); + foreach ($this->contents[$iParent] as $index => $val) + if ($iNode === $val) unset($this->contents[$iParent][$index]); + } + + // add a node to the mapping after the fact (e.g. created later in the process.) + function addNode($oNode) { + $iParent = $oNode->getParentNode(); + $this->mapnodes[$oNode->getId()] =& $oNode; + $this->contents[$oNode->getId()] = array(); + array_push($this->contents[$iParent], $oNode->getId()); + } + + function reparentKeyword($lookup_id, $destination_parent_id) + { + global $default; + + $oKeyword = $this->lookups[$lookup_id]; + $oldParent = $oKeyword->getTreeParent(); + $oNewParent = $this->mapnodes[$destination_parent_id]; + // we will have failed by here if its bogus. + //$default->log->debug('MDTree::reparentKeyword '.print_r($oNewParent, true)); + + // if its 0 or NULL, we reparent to null. + if (($oNewParent === null) or ($desintation_parent_id === 0)) { + $new_home = 0; + } else { + $new_home = $oNewParent->getId(); + } + $oKeyword->setTreeParent($new_home); + // don't assume we're reparenting from 0. + $KWIndex = array_search($lookup_id, $this->contents[$oldParent]["leaves"]); + unset($this->contents[$oldParent]["leaves"][$KWIndex]); + $this->contents[$new_home]["leaves"][] = $oKeyword->getId(); + $oKeyword->update(); + } + + + // STUB FUNCTIONS: need to be filled in. + + + // REALLY need to deprecate this, but how? + function render($bEditable) { return null; } // render using a template (with edit / buttons.) FIXME build a widget / renderer. + + + /* ----------------------- EVIL HACK -------------------------- + * + * This whole thing needs to replaced, as soon as I work out how + * to non-sucking Smarty recursion. + */ + + function _evilTreeRecursion($subnode, $treeToRender, $inputname) + { + $treeStr = "'; + return $treeStr; + + } + + // I can't seem to do recursion in smarty, and recursive templates seems a bad solution. + // Come up with a better way to do this (? NBM) + function _evilTreeRenderer($treeToRender, $inputname) { + //global $default; + $treeStr = ""; + $stack = array(); + $exitstack = array(); + + // since the root is virtual, we need to fake it here. + // the inner section is generised. + $treeStr .= ''; + + return $treeStr; + + } +} + +?> diff --git a/lib/documentmanagement/MetaData.inc b/lib/documentmanagement/MetaData.inc index e340720..1415faf 100644 --- a/lib/documentmanagement/MetaData.inc +++ b/lib/documentmanagement/MetaData.inc @@ -27,11 +27,13 @@ class MetaData extends KTEntity { /** primary key value */ - var $iId; + var $iId = -1; //document field id var $iDocFieldId; /** MetaData name */ var $sName; + /** _if_ this field is a tree, which node in said tree is this one's parent. */ + var $iTreeParent; /** * Default constructor @@ -40,68 +42,28 @@ class MetaData extends KTEntity { * @param MetaData data type * */ - function MetaData($iNewDocFieldID,$sNewName) { + function MetaData($iNewDocFieldID = null,$sNewName = null, $iNewParent = null) { //object not created yet $this->iId = -1; $this->iDocFieldID = $iNewDocFieldID; $this->sName = $sNewName; - - } - - /** - * Get the MetaData's primary key value - * - * @return int MetaData's primary key value - * - */ - function getID() { - return $this->iId; - } - - /** - * Get the MetaData's name - * - * @return String MetaData's name - * - */ - function getName() { - return $this->sName; + $this->iTreeParent = $iNewParent; } - - /** - * Set the MetaData's name - * - * @param MetaData's new name - * - */ - function setName($sNewValue) { - $this->sName = $sNewValue; - } - /** - * Set the MetaData's docField - * - * @param MetaData's new name - * - */ - function setDocFieldID($sNewValue) { - $this->iDocFieldID = $sNewValue; - } - - /** - * Get the MetaData's docfield - * - * @return String MetaData's name - * - */ - function getDocFieldID() { - return $this->iDocFieldID; - } + function getID() { return $this->iId; } + function getName() { return $this->sName; } + function setName($sNewValue) { $this->sName = $sNewValue; } + function getDocFieldID() { return $this->iDocFieldID; } + function setDocFieldID($iNewValue) { $this->iDocFieldID = $iNewValue; } + function getTreeParent() { return $this->iTreeParent; } + function setTreeParent($iNewValue) { $this->iTreeParent = $iNewValue; } + function _fieldValues () { return array( 'document_field_id' => $this->iDocFieldID, 'name' => $this->sName, + 'treeorg_parent' => $this->iTreeParent, ); } @@ -152,6 +114,7 @@ class MetaData extends KTEntity { if ($sql->next_record()) { $oDocumentType = & new MetaData($sql->f("document_field_id"),$sql->f("name")); $oDocumentType->iId = $sql->f("id"); + $oDocumentType->iTreeParent = $sql->f("treeorg_parent"); return $oDocumentType; } $_SESSION["errorMessage"] = $lang_err_object_not_exist . "id = " . $iDocumentID . " table = document_types"; diff --git a/lib/visualpatterns/PatternMetaData.inc b/lib/visualpatterns/PatternMetaData.inc index 4a3fd56..5a4dd90 100644 --- a/lib/visualpatterns/PatternMetaData.inc +++ b/lib/visualpatterns/PatternMetaData.inc @@ -1,6 +1,8 @@ document_fields_table WHERE name LIKE '" . DBUtil::escapeSimple($this->sMetaDataField) . "'";/*ok*/ + $sQuery = "SELECT has_lookup, data_type, has_lookuptree, id FROM $default->document_fields_table WHERE name LIKE '" . DBUtil::escapeSimple($this->sMetaDataField) . "'";/*ok*/ $sql = $default->db; $sql->query($sQuery); if ($sql->next_record()) { - if ($sql->f("has_lookup")) { + if ($sql->f("has_lookup") and (!$sql->f("has_lookuptree"))) { //is a lookup, so display a drop down list $sWhereClause = "DF.name LIKE '" . $this->sMetaDataField . "'"; $sFromClause = "INNER JOIN $default->document_fields_table AS DF ON ST.document_field_id = DF.id"; @@ -60,6 +62,10 @@ class PatternMetaData { $oPatternListBox->setFromClause($sFromClause); $oPatternListBox->setWhereClause($sWhereClause); return $oPatternListBox->render(); + } else if ($sql->f("has_lookup") and ($sql->f("has_lookuptree"))) { + $fieldTree = new MDTree(); + $fieldTree->buildForField($sql->f("id")); + return $fieldTree->_evilTreeRenderer($fieldTree, $this->sFormName); } else { $textboxlength = null; switch($sql->f("data_type")) { diff --git a/presentation/lookAndFeel/knowledgeTree/administration/docfieldmanagement/manageLookupTrees.php b/presentation/lookAndFeel/knowledgeTree/administration/docfieldmanagement/manageLookupTrees.php new file mode 100644 index 0000000..83b0900 --- /dev/null +++ b/presentation/lookAndFeel/knowledgeTree/administration/docfieldmanagement/manageLookupTrees.php @@ -0,0 +1,235 @@ +loadTemplate("ktcore/manage_lookuptrees"); + $aTemplateData = array( + "treefields" => $aTreeFields, + "lookupfields" => $aLookupFields, + ); + return $oTemplate->render($aTemplateData); + } + + function handleOutput($data) { + global $main; + $main->bFormDisabled = true; + $main->setCentralPayload($data); + $main->render(); + } + + function do_createTree() { + // extract. + $field_id = KTUtil::arrayGet($_REQUEST, 'field_id'); + + // validate + if (empty($field_id)) { return $this->errorRedirectToMain("Must select a field to convert."); } + $oField =& DocumentField::get($field_id); + if (PEAR::isError($oField)) { return $this->errorRedirectToMain("Invalid field."); } + + // set as a metadata tree. + $oField->setHasLookupTree(1); + $oField->update(); + $this->errorRedirectToMain("Converted ".$oField->getName()." to a tree."); + } + + + // create and display the tree editing form. + function do_editTree() { + global $default; + // extract. + $field_id = KTUtil::arrayGet($_REQUEST, 'field_id'); + $current_node = KTUtil::arrayGet($_REQUEST, 'current_node', 0); + $subaction = KTUtil::arrayGet($_REQUEST, 'subaction'); + + // validate + if (empty($field_id)) { return $this->errorRedirectToMain("Must select a field to edit."); } + $oField =& DocumentField::get($field_id); + if (PEAR::isError($oField)) { return $this->errorRedirectToMain("Invalid field."); } + + // under here we do the subaction rendering. + // we do this so we don't have to do _very_ strange things with multiple actions. + $default->log->debug("Subaction: " . $subaction); + $fieldTree =& new MDTree(); + $fieldTree->buildForField($oField->getId()); + + if ($subaction !== null) { + if ($subaction === "addCategory") { + $new_category = KTUtil::arrayGet($_REQUEST, 'category_name'); + if (empty($new_category)) { return $this->errorRedirectTo("editTree", "Must enter a name for the new category.", array("field_id" => $field_id)); } + else { $this->subact_addCategory($field_id, $current_node, $new_category, $fieldTree);} + } + if ($subaction === "deleteCategory") { + $this->subact_deleteCategory($fieldTree, $current_node); + $current_node = 0; // clear out, and don't try and render the newly deleted category. + } + if ($subaction === "linkKeywords") { + $keywords = KTUtil::arrayGet($_REQUEST, 'keywordsToAdd'); + $this->subact_linkKeywords($fieldTree, $current_node, $keywords); + $current_node = 0; // clear out, and don't try and render the newly deleted category. + } + if ($subaction === "unlinkKeyword") { + $keyword = KTUtil::arrayGet($_REQUEST, 'keyword_id'); + $this->subact_unlinkKeyword($fieldTree, $keyword); + } + } + + if ($fieldTree->root === null) { + return $this->errorRedirectToMain("Error building tree. Is this a valid tree-lookup field?"); + } + + // FIXME extract this from MDTree (helper method?) + $free_metadata = MetaData::getList('document_field_id = '.$oField->getId().' AND (treeorg_parent = 0 OR treeorg_parent IS NULL)'); + + // render edit template. + $oTemplating = new KTTemplating; + $oTemplate = $oTemplating->loadTemplate("ktcore/edit_lookuptrees"); + $renderedTree = $this->_evilTreeRenderer($fieldTree); + $aTemplateData = array( + "field" => $oField, + "tree" => $fieldTree, + "renderedTree" => $renderedTree, + "currentNode" => $current_node, + "freechildren" => $free_metadata, + ); + return $oTemplate->render($aTemplateData); + } + + function subact_addCategory($field_id, $current_node, $new_category, &$constructedTree) { + $newCategory = MDTreeNode::createFromArray(array ( + "iFieldId" => $field_id, + "sName" => $new_category, + "iParentNode" => $current_node, + )); + if (PEAR::isError($newCategory)) + { + return false; + } + $constructedTree->addNode($newCategory); + return true; + } + + function subact_deleteCategory(&$constructedTree, $current_node) { + $constructedTree->deleteNode($current_node); + return true; + } + + function subact_unlinkKeyword(&$constructedTree, $keyword) { + $oKW = MetaData::get($keyword); + $constructedTree->reparentKeyword($oKW->getId(), 0); + return true; + } + + + function subact_linkKeywords(&$constructedTree, $current_node, $keywords) { + foreach ($keywords as $md_id) + { + $constructedTree->reparentKeyword($md_id, $current_node); + } + return true; + } + + /* ----------------------- EVIL HACK -------------------------- + * + * This whole thing needs to replaced, as soon as I work out how + * to non-sucking Smarty recursion. + */ + + function _evilTreeRecursion($subnode, $treeToRender) + { + $treeStr = "'; + return $treeStr; + + } + + // I can't seem to do recursion in smarty, and recursive templates seems a bad solution. + // Come up with a better way to do this (? NBM) + function _evilTreeRenderer($treeToRender) { + //global $default; + $treeStr = ""; + $stack = array(); + $exitstack = array(); + + // since the root is virtual, we need to fake it here. + // the inner section is generised. + $treeStr .= ''; + + return $treeStr; + } + + // don't hate me. + function _evilActionHelper($iFieldId, $bIsKeyword, $current_node) { + $actionStr = " ("; + if ($bIsKeyword === true) { + $actionStr .= 'unlink'; + } + else + { + $actionStr .= 'add items '; + $actionStr .= '| delete'; + } + $actionStr .= ")"; + return $actionStr; + } + +} + +$oDispatcher = new ManageLookupTreeDispatcher(); +$oDispatcher->dispatch(); + +?> diff --git a/presentation/lookAndFeel/knowledgeTree/handleConditional.php b/presentation/lookAndFeel/knowledgeTree/handleConditional.php new file mode 100644 index 0000000..87fde05 --- /dev/null +++ b/presentation/lookAndFeel/knowledgeTree/handleConditional.php @@ -0,0 +1,287 @@ + rows. + + $oFieldSet = DocumentFieldSet::get($fieldset_id); + if (empty($active_fields)) { + $res = $this->getMasterFieldInfo($oFieldSet); + $fieldsToRender[$oFieldSet->getMasterField()] = $res; + } else { + // urgh. we use this to generate our list of things to extract from the input. + $pairings = array(); + foreach ($active_fields as $field_id) { + $current = KTUtil::arrayGet($_REQUEST, 'conditional_field_'.$field_id); + if ($current === null) { + return 'invalid input sequence.'; + } + else { + $pairings[$field_id] = $current; + } + + } + $res = $this->validatePath($oFieldSet, $pairings, true); + if ($res === true) { + return 'validated input.'; + } + else if ($res === false) { + return 'invalid input'; + } + // quick collation process. + foreach ($res as $aRow) { + $fieldsToRender[$aRow["document_field_id"]][] = $aRow; + } + } + + $default->log->debug('validatePath: results '.print_r($fieldsToRender,true)); + + $oTemplating = new KTTemplating; + + $oTemplate = $oTemplating->loadTemplate("ktcore/handle_conditional"); + $aTemplateData = array( + "fieldset" => $oFieldSet->getId(), + "oldfields" => $pairings, + "fieldsToRender" => $fieldsToRender, + ); + return $oTemplate->render($aTemplateData); + } + + function handleOutput($data) { + print $data; + } + + function getMasterFieldInfo($oFieldSet) { + global $default; + $masterField = $oFieldSet->getMasterField(); + $sQuery = "SELECT md_cond.document_field_id AS document_field_id, md_cond.metadata_lookup_id AS val, md_lookup.name AS name FROM $default->md_condition_table AS md_cond LEFT JOIN $default->md_condition_chain_table AS md_chain ON (md_cond.id = md_chain.child_condition) LEFT JOIN $default->metadata_table AS md_lookup ON (md_cond.metadata_lookup_id = md_lookup.id) WHERE md_cond.document_field_id = ? "; + return DBUtil::getResultArray(array($sQuery, array($masterField))); + } + + // either returns the set of pathid's for the input, + // or false (FIXME: should be PEAR::raiseError) + function validatePath($oFieldSet, $aPairings, $bPartial) { + /* An explanation of what happens below, since NBM's comment was "Put that crack-pipe back in the freezer." + * + * $aPairings are the inputs we were handed. Within these, there are 3 + * important things: + * 1. we have a limited set of document_fields within the $oFieldSet. + * 2. one of these fields is the "master field" - the _ONLY_ one without + * any parent conditions. + * 3. some fields may get passed _out_ as + * which indicates that there is NO VALUE for that field (again, saved in the db as "-1" so we don't go mad. + * + * We essentially have 3 stacks: inputs, parent_conditions, free_field_ids + * + * $bPartial indicates what we need to do once we've run out of inputs to validate: either fail (if we don't have coverage, and its not partial) + * or return the possible next choices. + * + * We start with the master-field, and get its entry in the table. If it isn't there, fail. + * i. push its field onto the "parent_conditions" stack, and remove the field_id from the "option_fields". + * ii. remove its entry from the inputs. + * While we have inputs -> + * find any matches that have a (field_id, lookup_id) in the input set, and parent_condition in the parent_conditions stack. + * if no values found, FAIL - invalid input (we can't match what we've been given). + * otherwise -> + * remove them from the input stack, push their id's onto the parent_condition stack, and remove their field_id's from the field_id stack. + * If $bPartial == true -> + * get anything which has a parent_condition in the parent_condition set, and a field_id in the free_field_id's set -> this will give you + * the next set of "inputs" - (column, lookup) pairs with parent-rules that have been activated. + * If $bPartial == false and free_field_id's still has anything in it WE FAIL OUT. + */ + global $default; + $free_field_ids = array(); + $parent_conditions = array(); + $inputs = $aPairings; // please tell me this does a copy ... + + // step 1: generate free_field_ids. + $childFields = DocumentField::getList('parent_fieldset = '.$oFieldSet->getId()); + foreach ($childFields as $oField) { + $free_field_ids[$oField->getId()] = 1; // placeholder. + } + $master_field = $oFieldSet->getMasterField(); // this is the id of the master field. + if (!array_key_exists($master_field, $inputs)) { + return false; // no master field in the input. + } + + // step 2: get the first parent, to get the ball rolling. + $sQuery = "SELECT * FROM $default->md_condition_table WHERE document_field_id = ? and metadata_lookup_id = ? "; + $aParams = array($master_field, $inputs[$master_field]); + $res = DBUtil::getOneResult(array($sQuery, $aParams)); + + if (PEAR::isError($res)) { + return false; // no value matched on the master field input, the rest MUST fail. + } + else { + unset($free_field_ids[$master_field]); // master is no longer free. + $rule_id = $res["id"]; + $parent_conditions[$rule_id] = 1; + unset($inputs[$master_field]); + } + + $default->log->debug('validatePath: parent_conditions '.print_r($parent_conditions,true)); + + + while (count($inputs) != 0) { // we'll return out inside here if necessary. + // check for items in inputs, with parents in parent_conditions. + + // $testStr = ""; + // $testarr = array(); + // for ($i=0; $i<3; $i++) { + // $testarr[] = "( ? )"; + // } + // $testStr = "(".join(" OR ", $testarr).")"; + // return $testStr; + + // we need something like "parent_conditions IN (1,2,3) AND ((f=1 AND v=2) OR (f=2 AND v=5) OR (f=4 AND v=7)) + $sParentClause = "md_chain.parent_condition IN ("; + $aInputParts = array(); + for ($i=0; $i $lookid) { + $aInputs[] = $fid; + $aInputs[] = $lookid; + $aInputParts[] = '(md_cond.document_field_id = ? AND md_cond.metadata_lookup_id = ?)'; + } + $sInputs = join(" OR ", $aInputParts); + $sInputs = '(' . $sInputs . ')'; + + + $default->log->debug('validatePath: parent_conditions '.print_r($parent_conditions,true)); + + $sFieldClause = "md_cond.document_field_id IN ("; + for ($i=0; $ilog->debug('validatePath: sParentClause '.print_r($sParentClause,true)); + + $sWhere = KTUtil::whereToString(array(array($sParentClause, array_keys($parent_conditions)), array($sInputs, $aInputs))); + $sQuery = "SELECT md_cond.id as rule_id, md_cond.document_field_id AS field_id FROM $default->md_condition_table AS md_cond LEFT JOIN $default->md_condition_chain_table AS md_chain ON (md_cond.id = md_chain.child_condition) WHERE "; + $default->log->debug('validatePath: '.print_r(array($sQuery . $sWhere[0], $sWhere[1]),true)); + $res = DBUtil::getResultArray(array($sQuery . $sWhere[0], $sWhere[1])); + if (PEAR::isError($res)) { + return false; + } + + // if there's anything is $res, its a match (must be - we can't have crossed chains.) + if (count($res) == 0) { + return false; // fail - no matches from inputs against parent_conditions. + } else { + // we must have a match - MAY have multiple matches. + foreach ($res as $aRow) { + $default->log->debug('validatePath: output_row '.print_r($aRow,true)); + $parent_conditions[$aRow["rule_id"]] = 1; // add this as a possible parent condition. + unset($free_field_ids[$aRow["field_id"]]); // no longer free + unset($inputs[$aRow["field_id"]]); // no longer an un-processed input, so reduce the input-count. + } + } + } + + // ok: we got this far, and have run out of inputs without running out of matches. + // IF we're looking for a partial match, and still have free fields, return the set of free-and-parent matches. + // OTHERWISE if we have free fields, fail + // finally, pass the input as valid. + + if (($bPartial === true) and (count($free_field_ids) > 0)) { + // generate the set of matches for free_fields with appropriate parents. + // UNFORTUNATELY, there is no "nice" way to do this. + // Wax On. Wax Off. + + $sParentClause = "md_chain.parent_condition IN ("; + for ($i=0; $ilog->debug('validatePath: parent_conditions '.print_r($parent_conditions,true)); + + $sFieldClause = "md_cond.document_field_id IN ("; + for ($i=0; $ilog->debug('validatePath: sParentClause '.print_r($sParentClause,true)); + + $aWhere = KTUtil::whereToString(array(array($sParentClause, array_keys($parent_conditions)), array($sFieldClause,array_keys($free_field_ids)))); + $sQuery = "SELECT md_cond.document_field_id AS document_field_id, md_cond.metadata_lookup_id AS val, md_lookup.name as name FROM $default->md_condition_table AS md_cond LEFT JOIN $default->md_condition_chain_table AS md_chain ON (md_cond.id = md_chain.child_condition) LEFT JOIN $default->metadata_table AS md_lookup ON (md_cond.metadata_lookup_id = md_lookup.id) WHERE "; + $default->log->debug('validatePath: sParentClause '.print_r(array($sQuery . $aWhere[0], $aWhere[1]), true)); + $sOrderClause = " ORDER BY document_field_id ASC, name ASC"; + return DBUtil::getResultArray(array($sQuery . $aWhere[0] . $sOrderClause, $aWhere[1])); // FIXME catch errors? + } else if (count($free_field_ids) != 0) { + return false; // incomplete - could actually catch this at the start. + } else { + return true; // note - this ALSO matches whenever $bPartial is true, but we have completed. UP THE STACK: true => valid and committable. + } + } + + // FIXME: this need s to move into MDCondition.inc, or manageConditionalMetadata.php + // actually, this needs to die - its DEBUG only, really. + function do_createCondition() { + global $default; + $fieldset_id = KTUtil::arrayGet($_REQUEST, 'fieldset_id'); + $parent_id = KTUtil::arrayGet($_REQUEST, 'parent_id'); + $field_id = KTUtil::arrayGet($_REQUEST, 'field_id'); + $lookup_id = KTUtil::arrayGet($_REQUEST, 'lookup_id'); + + $resObj = MDConditionNode::createFromArray(array( + "iFieldId" => $field_id, + "iLookupId" => $lookup_id, + )); + + $default->log->debug("CREATE_CONDITION_DEBUG: ".print_r($resObj,true)); + + $resObj2 = MDConditionChain::createFromArray(array( + "iParentCondition" => $parent_id, // may be null. + "iChildCondition" => $resObj->getId(), + )); + + $default->log->debug("CREATE_CONDITION_DEBUG: ".print_r($resObj2,true)); + } + +} + +$oDispatcher = new HandleConditionalDispatcher(); +$oDispatcher->dispatch(); + +?>