. * * You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco, * California 94120-7775, or email info@knowledgetree.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU General Public License version 3. * * In accordance with Section 7(b) of the GNU General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * KnowledgeTree" logo and retain the original copyright notice. If the display of the * logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices * must display the words "Powered by KnowledgeTree" and retain the original * copyright notice. * Contributor( s): ______________________________________ * */ require_once(KT_LIB_DIR . "/ktentity.inc"); require_once(KT_LIB_DIR . '/documentmanagement/MetaData.inc'); require_once(KT_LIB_DIR . '/documentmanagement/DocumentField.inc'); require_once(KT_LIB_DIR . '/metadata/valueinstance.inc.php'); require_once(KT_LIB_DIR . '/metadata/fieldset.inc.php'); require_once(KT_LIB_DIR . '/metadata/fieldbehaviour.inc.php'); class KTMetadataUtil { // {{{ _getNextForBehaviour /** * Recursively traverses a fieldset from the behaviour assigned to * the master field and thereafter subsequent fields uncovering the * fields that should be filled in given the columns/fields that * have already be filled in, and providing the values for them * based on the behaviours specified by the existing values in their * parent fields (in combination with _their_ parent fields). */ function _getNextForBehaviour($oBehaviour, $aCurrentSelections) { /* * GENERAL GAME PLAN * * For this behaviour, get the fields that this behaviour * affects. Also get the values that this behaviour prescribes * for those fields. * * Then, for each of the next fields, check if they are already * filled in. * * If not, leave that field in the set of values that need to be * filled in, and move on to the next field for this behaviour. * * If it is filled in, remove the field from the set of values * to be filled in. But add the set of fields and values that * the choice of value in this field prescribe (using a * recursive call to this function). */ $oBehaviour =& KTUtil::getObject('KTFieldBehaviour', $oBehaviour); if(PEAR::isError($oBehaviour)) { return array(); } $GLOBALS['default']->log->debug('KTMetadataUtil::_getNextForBehaviour, behaviour is ' . $oBehaviour->getId()); $aValues = KTMetadataUtil::getNextValuesForBehaviour($oBehaviour); $iFieldId = $oBehaviour->getFieldId(); $aNextFields = KTMetadataUtil::getChildFieldIds($iFieldId); $GLOBALS['default']->log->debug('KTMetadataUtil::_getNextForBehaviour, next fields for ' . $iFieldId . ' are: ' . print_r($aNextFields, true)); foreach ($aNextFields as $iThisFieldId) { if (!in_array($iThisFieldId, array_keys($aCurrentSelections))) { $GLOBALS['default']->log->debug('KTMetadataUtil::_getNextForBehaviour, field ' . $iThisFieldId . ' is not selected'); } else { $GLOBALS['default']->log->debug('KTMetadataUtil::_getNextForBehaviour, field ' . $iThisFieldId . ' is selected'); unset($aValues[$iThisFieldId]); // here we need a Lookup field. if (!is_a($aCurrentSelections[$iThisFieldId], 'MetaData')) { $oL = MetaData::getByValueAndDocumentField($aCurrentSelections[$iThisFieldId], $iThisFieldId); } else { $oL = $aCurrentSelections[$iThisFieldId]; } $oInstance = KTValueInstance::getByLookupAndParentBehaviour($oL, $oBehaviour); if (is_null($oInstance)) { // somehow an invalid value hit us here. continue; } $GLOBALS['default']->log->debug('KTMetadataUtil::_getNextForBehaviour, instance is ' . print_r($oInstance, true)); $nextBehaviour = $oInstance->getBehaviourId(); // may be NULL if (!is_null($nextBehaviour)) { $oChildBehaviour =& KTFieldBehaviour::get($nextBehaviour); $GLOBALS['default']->log->debug('KTMetadataUtil::_getNextForBehaviour, field ' . $iThisFieldId . ' is not selected'); $aMyValues = KTMetadataUtil::_getNextForBehaviour($oChildBehaviour, $aCurrentSelections); $GLOBALS['default']->log->debug('KTMetadataUtil::_getNextForBehaviour, values are ' . print_r($aMyValues, true)); foreach ($aMyValues as $k => $v) { $aValues[$k] = $v; } } } } $GLOBALS['default']->log->debug('KTMetadataUtil::_getNextForBehaviour, final values are ' . print_r($aValues, true)); // now we need to clean this up: remove no-value entries. $temp = $aValues; foreach ($temp as $k => $v) { if (empty($v)) { unset($aValues[$k]); } } return $aValues; } // }}} // {{{ getNext /** * Given a set of selected values (aCurrentSelections) for a given * field set (iFieldSet), returns an array (possibly empty) with the * keys set to newly uncovered fields, and the contents an array of * the value instances that are available to choose in those fields. * * Return value: * * array( * array('field' => DocumentField, 'values' => array(Metadata, Metadata)), * ... * ) * */ function getNext($oFieldset, $aCurrentSelections) { /* * GENERAL GAME PLAN * * Firstly, if there are no current selections, return the * master field and all of its values. * * If there are selections, get the behaviour for the selected * value of the master field, and call _getNextForBehaviour on * it, passing in the current selections. This will return an * array keyed on field id with values of an array of lookup ids * for that field. * * Convert these to objects and the return format. */ $oFieldset =& KTUtil::getObject('KTFieldset', $oFieldset); $GLOBALS['default']->log->debug('KTMetadataUtil::getNext, selections are: ' . print_r($aCurrentSelections, true)); if (empty($aCurrentSelections)) { $oField =& DocumentField::get($oFieldset->getMasterFieldId()); if (PEAR::isError($oField)) { return array(); } // FIXME we now need: the VALUES of MasterField that are assigned to a behaviour. (ONLY) return array($oField->getId() => array('field' => $oField, 'values' => MetaData::getEnabledByDocumentField($oField))); } $oMasterField =& DocumentField::get($oFieldset->getMasterFieldId()); if (PEAR::isError($oMasterField)) { return array(); } $aSelectedFields = array_keys($aCurrentSelections); $field = $oMasterField->getId(); $val = $aCurrentSelections[$field]; $lookup = MetaData::getByValueAndDocumentField($val, $field); //var_dump($lookup); exit(0); $oValueInstance = KTValueInstance::getByLookupSingle($lookup); if (PEAR::isError($oValueInstance) || is_null($oValueInstance) || ($oValueInstance === false)) { return true; } // throw an error $aValues = KTMetadataUtil::_getNextForBehaviour($oValueInstance->getBehaviourId(), $aCurrentSelections); $GLOBALS['default']->log->debug('KTMetadataUtil::getNext, values are ' . print_r($aValues, true)); $aReturn = array(); foreach ($aValues as $iFieldId => $aValueIds) { $aTheseValues = array(); foreach ($aValueIds as $iLookupId) { $aTheseValues[$iLookupId] = MetaData::get($iLookupId); } $aReturn[$iFieldId] = array( 'field' => DocumentField::get($iFieldId), 'values' => $aTheseValues, ); } return $aReturn; } // }}} // {{{ getMasterField /** * A conditional fieldset has a single field which is not affected * by other values. This is the master field. This function gets * the master field for the fieldset provided. */ function getMasterField($oFieldset) { $oFieldset =& KTUtil::getObject('KTFieldset', $oFieldset); if ($oFieldset->getMasterField()) { return DocumentField::get($oFieldset->getMasterField()); } } // }}} // {{{ removeSetsFromDocumentType /** * Removes a non-generic fieldset from a given document type. * * (Generic fieldsets are made available to and are required for all * (subsequent) documents. Non-generic fieldsets are made available * to and are required for all (subsequent) documents that have a * particular document type.) */ function removeSetsFromDocumentType($oDocumentType, $aFieldsets) { if (is_object($oDocumentType)) { $iDocumentTypeId = $oDocumentType->getId(); } else { $iDocumentTypeId = $oDocumentType; } if (!is_array($aFieldsets)) { $aFieldsets = array($aFieldsets); } if (empty($aFieldsets)) { return true; } $aIds = array(); foreach ($aFieldsets as $oFieldset) { if (is_object($oFieldset)) { $iFieldsetId = $oFieldset->getId(); } else { $iFieldsetId = $oFieldset; } $aIds[] = $iFieldsetId; } // Converts to (?, ?, ?) for query $sParam = DBUtil::paramArray($aIds); $aWhere = KTUtil::whereToString(array( array('document_type_id = ?', array($iDocumentTypeId)), array("fieldset_id IN ($sParam)", $aIds), )); $sTable = KTUtil::getTableName('document_type_fieldsets'); $aQuery = array( "DELETE FROM $sTable WHERE {$aWhere[0]}", $aWhere[1], ); return DBUtil::runQuery($aQuery); } // }}} // {{{ addSetsToDocumentType /** * Adds a non-generic fieldset to a given document type. * * (Generic fieldsets are made available to and are required for all * (subsequent) documents. Non-generic fieldsets are made available * to and are required for all (subsequent) documents that have a * particular document type.) */ function addSetsToDocumentType($oDocumentType, $aFieldsets) { if (is_object($oDocumentType)) { $iDocumentTypeId = $oDocumentType->getId(); } else { $iDocumentTypeId = $oDocumentType; } if (!is_array($aFieldsets)) { $aFieldsets = array($aFieldsets); } $aIds = array(); foreach ($aFieldsets as $oFieldset) { if (is_object($oFieldset)) { $iFieldsetId = $oFieldset->getId(); } else { $iFieldsetId = $oFieldset; } $aIds[] = $iFieldsetId; } $sTable = KTUtil::getTableName('document_type_fieldsets'); foreach ($aIds as $iId) { $res = DBUtil::autoInsert($sTable, array( 'document_type_id' => $iDocumentTypeId, 'fieldset_id' => $iId, )); if (PEAR::isError($res)) { return $res; } } return true; } // }}} // {{{ addFieldOrder /** * Informs the system that the parent field's values in affects the * child field's values in a conditional fieldset. */ function addFieldOrder($oParentField, $oChildField, $oFieldset) { $iParentFieldId = KTUtil::getId($oParentField); $iChildFieldId = KTUtil::getId($oChildField); $iFieldsetId = KTUtil::getId($oFieldset); $aOptions = array('noid' => true); $sTable = KTUtil::getTableName('field_orders'); $aValues = array( 'parent_field_id' => $iParentFieldId, 'child_field_id' => $iChildFieldId, 'fieldset_id' => $iFieldsetId, ); return DBUtil::autoInsert($sTable, $aValues, $aOptions); } // }}} // {{{ removeFieldOrdering /** * Removes all field ordering for the given fieldset. */ function removeFieldOrdering($oFieldset) { $iFieldsetId = KTUtil::getId($oFieldset); $sTable = KTUtil::getTableName('field_orders'); $aQuery = array( "DELETE FROM $sTable WHERE fieldset_id = ?", array($iFieldsetId), ); return DBUtil::runQuery($aQuery); } // }}} // {{{ getParentFieldId /** * In a conditional fieldset, a field's values is affected by a * single parent field's values in an ordered fashion (unless it is * the root/master field). This function gets the field id for the * field that this field is affected by. */ function getParentFieldId($oField) { $sTable = KTUtil::getTableName('field_orders'); $aQuery = array("SELECT parent_field_id FROM $sTable WHERE child_field_id = ?", array($oField->getId()), ); return DBUtil::getOneResultKey($aQuery, 'parent_field_id'); } // }}} // {{{ getChildFieldIds /** * In a conditional fieldset, a field's values affect other fields' * values in an ordered fashion. This function gets the field ids * for the fields that this field affects. */ function getChildFieldIds($oField) { $iFieldId = KTUtil::getId($oField); $sTable = KTUtil::getTableName('field_orders'); $aQuery = array("SELECT child_field_id FROM $sTable WHERE parent_field_id = ?", array($iFieldId), ); return DBUtil::getResultArrayKey($aQuery, 'child_field_id'); } // }}} // {{{ getOrCreateValueInstanceForLookup /** * Used as a helper function in simple conditional fieldset * administration, this function either returns the existing value * instance for a lookup, or creates a new value instance and * returns that. */ function &getOrCreateValueInstanceForLookup(&$oLookup) { $oLookup =& KTUtil::getObject('MetaData', $oLookup); $oValueInstance =& KTValueInstance::getByLookupSingle($oLookup); if (PEAR::isError($oValueInstance)) { return $oValueInstance; } // If we got a value instance, return it. if (!is_null($oValueInstance)) { return $oValueInstance; } // Else create one and return it. return KTValueInstance::createFromArray(array( 'fieldid' => $oLookup->getDocFieldId(), 'fieldvalueid' => $oLookup->getId(), )); } // }}} // {{{ getNextValuesForLookup /** * Used as a helper for simple conditional fieldset administration, * this function returns an array of lookup ids (Metadata->id) for * each of the columns/fields that this lookup's column affects. * * Return value: * * Associative array keyed by field_id, value is an array of lookup * ids. * * array( * 1 => array(1, 2, 3, 4), * ... * ); */ function getNextValuesForLookup($oLookup) { /* * GENERAL GAME PLAN * * Get the instance attached to the lookup, and and call * getNextValuesForBehaviour on its behaviour. * * If there's no instance or behaviour, return an empty array * for each field that the lookup's field affects. */ $oLookup =& KTUtil::getObject('MetaData', $oLookup); $oInstance =& KTValueInstance::getByLookupSingle($oLookup); if (PEAR::isError($oInstance)) { $GLOBALS['default']->log->error('KTMetadataUtil::getNextValuesForLookup, got dud instance id, returned: ' . print_r($oInstance, true)); return $oInstance; } if (!is_null($oInstance) && $oInstance->getBehaviourId()) { // if we have an instance, and we have a behaviour, return // the actual values for that behaviour. $oBehaviour =& KTFieldBehaviour::get($oInstance->getBehaviourId()); if (PEAR::isError($oBehaviour)) { $GLOBALS['default']->log->error('KTMetadataUtil::getNextValuesForLookup, got dud behaviour id, returned: ' . print_r($oBehaviour, true)); return $res; } return KTMetadataUtil::getNextValuesForBehaviour($oBehaviour); } // No instance or no behaviour, so send an empty array for each // field that we affect. $aChildFieldIds = KTMetadataUtil::getChildFieldIds($oLookup->getDocFieldId()); if (PEAR::isError($aChildFieldIds)) { $GLOBALS['default']->log->error('KTMetadataUtil::getNextValuesForLookup, getChildFieldIds returned: ' . print_r($aChildFieldIds, true)); return $res; } foreach ($aChildFieldIds as $iFieldId) { $aValues[$iFieldId] = array(); } return $aValues; } // }}} // {{{ getNextValuesForBehaviour /** * Given a behaviour, return an array of lookup ids (Metadata->id) * that are available for each of the columns/fields that this * behaviour's column affects. * * Return value: * * Associative array keyed by field_id, value is an array of lookup * ids. * * array( * 1 => array(1, 2, 3, 4), * ... * ); */ function getNextValuesForBehaviour($oBehaviour) { $oBehaviour =& KTUtil::getObject('KTFieldBehaviour', $oBehaviour); $aValues = array(); $sTable = KTUtil::getTableName('field_behaviour_options'); $aChildFieldIds = KTMetadataUtil::getChildFieldIds($oBehaviour->getFieldId()); foreach ($aChildFieldIds as $iFieldId) { $aValues[$iFieldId] = array(); } $aQuery = array( "SELECT field_id, instance_id FROM $sTable WHERE behaviour_id = ?", array($oBehaviour->getId()), ); $aRows = DBUtil::getResultArray($aQuery); if (PEAR::isError($aRows)) { return $aRows; } foreach ($aRows as $aRow) { $oInstance =& KTValueInstance::get($aRow['instance_id']); // need to wean out the disabled values. // now get the metadata value. $oMetadata = MetaData::get($oInstance->getFieldValueId()); if (PEAR::isError($oMetadata)) { continue; // invalid link. bugger. } if ($oMetadata->getDisabled()) { continue; // disabled. } $aValues[$aRow['field_id']][] = $oInstance->getFieldValueId(); } return $aValues; } // }}} // {{{ function validateCompleteness($oFieldset) { $res = KTMetadataUtil::checkConditionalFieldsetCompleteness($oFieldset); // errors, false, or null are all false. if ($res === true) { return true; } return false; } // }}} // {{{ checkConditionalFieldsetCompleteness /** * Checks whether a conditional fieldset has the necessary * relationships set up to be usable - this means that for each * field, no matter how it is reached, there is at least one option * available to choose. */ function checkConditionalFieldsetCompleteness($oFieldset) { $oFieldset =& KTUtil::getObject('KTFieldset', $oFieldset); if ($oFieldset->getIsConditional() == false) { // If we're not conditional, we are fine. return true; } /* * First, ensure at least one master field item has a behaviour * assigned to it. That allows at least one item in the master * field to be chosen. */ $iMasterFieldId = $oFieldset->getMasterFieldId(); $sTable = KTUtil::getTableName('field_value_instances'); $sLookupTable = KTUtil::getTableName('metadata_lookup'); $aQuery = array( "SELECT COUNT(FVI.id) AS cnt FROM $sTable AS FVI LEFT JOIN $sLookupTable AS ML ON (FVI.field_value_id = ML.id) WHERE FVI.field_id = ? AND ML.disabled = 0", array($iMasterFieldId), ); $iCount = DBUtil::getOneResultKey($aQuery, 'cnt'); if (PEAR::isError($iCount)) { return $iCount; } $GLOBALS['default']->log->debug("Number of value instances for master field: $iCount"); if ($iCount == 0) { $GLOBALS['default']->log->debug("Number of value instances for master field is zero, failing"); return PEAR::raiseError(_kt("Master field has no values which are assigned to behaviours.")); } $GLOBALS['default']->log->debug("Number of value instances for master field is positive, continuing"); // fix for KTS-1023 // check that each master-field value has a valueinstance assigned. $sTable = KTUtil::getTableName('metadata_lookup'); $aQuery = array( "SELECT COUNT(id) AS cnt FROM $sTable WHERE document_field_id = ? AND disabled = 0 ", array($iMasterFieldId), ); $iValCount = DBUtil::getOneResultKey($aQuery, 'cnt'); // assumes that there cannot be more than 1 value instance for each master-field-value. if ($iValCount != $iCount) { return PEAR::raiseError(sprintf(_kt('%d values for the Master Field are not assigned to behaviours.'), ($iValCount - $iCount))); } /* * Plan: For each behaviour that is assigned on the system, * ensure that it allows at least one value instance in each of * the fields that it needs to affect. */ $sTable = KTUtil::getTableName('field_value_instances'); $sFieldTable = KTUtil::getTableName('document_fields'); $aQuery = array( "SELECT DISTINCT FV.behaviour_id AS behaviour_id FROM $sTable AS FV INNER JOIN $sFieldTable AS F ON FV.field_id = F.id WHERE F.parent_fieldset = ? AND FV.behaviour_id IS NOT NULL", array($oFieldset->getId()), ); $aBehaviourIds = DBUtil::getResultArrayKey($aQuery, 'behaviour_id'); if (PEAR::isError($aBehaviourIds)) { return $aBehaviourIds; } foreach ($aBehaviourIds as $iBehaviourId) { $GLOBALS['default']->log->debug("Checking behaviour id: " . $iBehaviourId); $oBehaviour =& KTFieldBehaviour::get($iBehaviourId); $sBehaviourName = $oBehaviour->getName(); $sBehaviourHumanName = $oBehaviour->getHumanName(); $iParentFieldId = $oBehaviour->getFieldId(); $GLOBALS['default']->log->debug(" field is " . $iParentFieldId); $aNextFields = KTMetadataUtil::getChildFieldIds($iParentFieldId); $oParentField =& DocumentField::get($iParentFieldId); $sParentFieldName = $oParentField->getName(); $GLOBALS['default']->log->debug(" next fields must include " . print_r($aNextFields, true)); $sTable = KTUtil::getTableName('field_behaviour_options'); $aQuery = array( "SELECT DISTINCT field_id FROM $sTable WHERE behaviour_id = ?", array($iBehaviourId), ); $aFields = DBUtil::getResultArrayKey($aQuery, 'field_id'); $GLOBALS['default']->log->debug(" actual fields are " . print_r($aNextFields, true)); /* foreach ($aNextFields as $iFieldId) { if (!in_array($iFieldId, $aFields)) { $GLOBALS['default']->log->debug(" field $iFieldId is not included, failing"); $oChildField =& DocumentField::get($iFieldId); $sChildFieldName = $oChildField->getName(); return PEAR::raiseError("Child field $sChildFieldName of parent field $sParentFieldName has no selectable values in behaviour $sBehaviourHumanName ($sBehaviourName)"); } */ } $GLOBALS['default']->log->debug("Got through: passed!"); return true; } // }}} // {{{ synchroniseMetadata /** * This function takes a list of metadata values and synchronises * those values into the values that already exist for the field by * adding new values and disabling values that aren't in the new * list. * * XXX: Scalability: This function */ function synchroniseMetadata($oField, $aNewMetadata) { $iFieldId = KTUtil::getId($oField); $aCurrentAllValues = Metadata::getValuesByDocumentField($iFieldId); $aCurrentEnabledValues = Metadata::getEnabledValuesByDocumentField($iFieldId); $aCurrentDisabledValues = Metadata::getDisabledValuesByDocumentField($iFieldId); $aToBeAddedValues = array_diff($aNewMetadata, $aCurrentAllValues); $aToBeDisabledValues = array_diff($aCurrentEnabledValues, $aNewMetadata); $aToBeEnabledValues = array_intersect($aCurrentDisabledValues, $aNewMetadata); foreach ($aToBeAddedValues as $sValue) { $oMetadata =& Metadata::createFromArray(array( 'name' => $sValue, 'docfieldid' => $iFieldId, )); } foreach ($aToBeDisabledValues as $sValue) { $oMetadata =& Metadata::getByValueAndDocumentField($sValue, $iFieldId); if (PEAR::isError($oMetadata)) { var_dump($aToBeDisabledValues); var_dump($sValue); var_dump($iFieldId); var_dump($oMetadata); exit(0); } if (!$oMetadata->getIsStuck()) { $oMetadata->updateFromArray(array( 'disabled' => true, )); } } foreach ($aToBeEnabledValues as $sValue) { $oMetadata =& Metadata::getByValueAndDocumentField($sValue, $iFieldId); if (!$oMetadata->getIsStuck()) { $oMetadata->updateFromArray(array( 'disabled' => false, )); } } } // }}} // {{{ fieldsetsForDocument function fieldsetsForDocument($oDocument, $iTypeOverride = null) { global $default; $oDocument = KTUtil::getObject('Document', $oDocument); $iMetadataVersionId = $oDocument->getMetadataVersionId(); $iDocumentTypeId = $oDocument->getDocumentTypeId(); if (!is_null($iTypeOverride)) { $iDocumentTypeId = $iTypeOverride; } $sQuery = "SELECT DISTINCT F.id AS fieldset_id " . "FROM $default->document_metadata_version_table AS DM INNER JOIN document_fields_link AS DFL ON DM.id = DFL.metadata_version_id " . "INNER JOIN $default->document_fields_table AS DF ON DF.ID = DFL.document_field_id " . "INNER JOIN $default->fieldsets_table AS F ON F.id = DF.parent_fieldset " . "WHERE DM.id = ?" . "AND F.disabled = false"; $aParam = array($iMetadataVersionId); $aDocumentFieldsetIds = DBUtil::getResultArrayKey(array($sQuery, $aParam), 'fieldset_id'); $aGenericFieldsetIds = KTFieldset::getGenericFieldsets(array('ids' => true)); $aSpecificFieldsetIds = KTFieldset::getForDocumentType($iDocumentTypeId, array('ids' => true)); $aFieldsetIds = kt_array_merge($aDocumentFieldsetIds, $aGenericFieldsetIds, $aSpecificFieldsetIds); $aFieldsetIds = array_unique($aFieldsetIds); sort($aFieldsetIds); $aRet = array(); foreach ($aFieldsetIds as $iID) { $aRet[] = call_user_func(array('KTFieldset', 'get'), $iID); } return $aRet; } // }}} } ?>