* @access protected
*/
function initialize() {
- $links = $this->links();
- if (empty($links)) {
- return;
- }
-
$this->_connect();
-
- if (!isset($GLOBALS['_DB_DATAOBJECT']['LINKS'][$this->_database])) {
- $GLOBALS['_DB_DATAOBJECT']['LINKS'][$this->_database] = array();
- }
-
- if (!array_key_exists($this->__table, $GLOBALS['_DB_DATAOBJECT']['LINKS'][$this->_database])) {
- $GLOBALS['_DB_DATAOBJECT']['LINKS'][$this->_database][$this->__table] = $links;
- }
}
/**
/**
* returns list of FK relationships
*
+ * @static
* @access public
*
- * @return array
+ * @return array of CRM_Core_EntityReference
*/
- function links() {
- return NULL;
+ static function getReferenceColumns() {
+ return array();
}
/**
}
}
- // set the links
- $this->links();
-
return $table;
}
}
/**
- * Check the tables sent in, to see if there are any tables where there is a value for
- * a column
+ * Find all records which refer to this entity.
+ *
+ * @return array of objects referencing this
+ */
+ function findReferences() {
+ $links = self::getReferencesToTable(static::getTableName());
+
+ $occurrences = array();
+ foreach ($links as $refSpec) {
+ $refColumn = $refSpec->getReferenceKey();
+ $targetColumn = $refSpec->getTargetKey();
+ $params = array(1 => array($this->$targetColumn, 'String'));
+ $sql = <<<EOS
+SELECT id
+FROM {$refSpec->getReferenceTable()}
+WHERE {$refColumn} = %1
+EOS;
+ if ($refSpec->isGeneric()) {
+ $params[2] = array(static::getTableName(), 'String');
+ $sql .= <<<EOS
+ AND {$refSpec->getTypeColumn()} = %2
+EOS;
+ }
+ $daoName = CRM_Core_AllCoreTables::getClassForTable($refSpec->getReferenceTable());
+ $result = self::executeQuery($sql, $params, TRUE, $daoName);
+ while ($result->fetch()) {
+ $obj = new $daoName();
+ $obj->id = $result->id;
+ $occurrences[] = $obj;
+ }
+ }
+
+ return $occurrences;
+ }
+
+ /**
+ * List all tables which have hard foreign keys to this table.
*
- * This is typically used when we want to delete a row, but want to avoid the FK errors
- * that it might cause due to this being a required FK
+ * For now, this returns a description of every entity_id/entity_table
+ * reference.
+ * TODO: filter dynamic entity references on the $tableName, based on
+ * schema metadata in dynamicForeignKey which enumerates a restricted
+ * set of possible entity_table's.
*
- * @param array an array of values (tableName, columnName)
- * @param array the parameter array with the value and type
- * @param array (reference) the tables which had an entry for this value
+ * @param string $tableName table referred to
*
- * @return boolean true if no value exists in all the tables
- * @static
+ * @return array structure of table and column, listing every table with a
+ * foreign key reference to $tableName, and the column where the key appears.
*/
- public static function doesValueExistInTable(&$tables, $params, &$errors) {
- $errors = array();
- foreach ($tables as $table) {
- $sql = "SELECT count(*) FROM {$table['table']} WHERE {$table['column']} = %1";
- $count = self::singleValueQuery($sql, $params);
- if ($count > 0) {
- $errors[$table['table']] = $count;
+ static function getReferencesToTable($tableName) {
+ $refsFound = array();
+ foreach (CRM_Core_AllCoreTables::getClasses() as $daoClassName) {
+ $links = $daoClassName::getReferenceColumns();
+ $daoTableName = $daoClassName::getTableName();
+
+ foreach ($links as $refSpec) {
+ if ($refSpec->getTargetTable() === $tableName
+ or $refSpec->isGeneric()
+ ) {
+ $refsFound[] = $refSpec;
+ }
}
}
-
- return (empty($errors)) ? FALSE : TRUE;
+ return $refsFound;
}
/**
--- /dev/null
+<?php
+
+/**
+ * Description of a one-way link between two entities
+ *
+ * This could be a foreign key or a generic (entity_id, entity_table) pointer
+ */
+class CRM_Core_EntityReference {
+ protected $refTable;
+ protected $refKey;
+ protected $refTypeColumn;
+ protected $targetTable;
+ protected $targetKey;
+
+ function __construct($refTable, $refKey, $targetTable = NULL, $targetKey = 'id', $refTypeColumn = NULL) {
+ $this->refTable = $refTable;
+ $this->refKey = $refKey;
+ $this->targetTable = $targetTable;
+ $this->targetKey = $targetKey;
+ $this->refTypeColumn = $refTypeColumn;
+ }
+
+ function getReferenceTable() {
+ return $this->refTable;
+ }
+
+ function getReferenceKey() {
+ return $this->refKey;
+ }
+
+ function getTypeColumn() {
+ return $this->refTypeColumn;
+ }
+
+ function getTargetTable() {
+ return $this->targetTable;
+ }
+
+ function getTargetKey() {
+ return $this->targetKey;
+ }
+
+ /**
+ * @return true if the reference can point to more than one type
+ */
+ function isGeneric() {
+ return ($this->refTypeColumn !== NULL);
+ }
+}
* @static
*/
static function del($financialTypeId) {
- //checking if financial type is present
- $check = false;
-
- // ensure that we have no objects that have an FK to this financial type id that cannot be null
- $tables =
- array(
- array(
- 'table' => 'civicrm_contribution',
- 'column' => 'financial_type_id'
- ),
- array(
- 'table' => 'civicrm_contribution_page',
- 'column' => 'financial_type_id'
- ),
- array(
- 'table' => 'civicrm_contribution_recur',
- 'column' => 'financial_type_id'
- ),
- array(
- 'table' => 'civicrm_membership_type',
- 'column' => 'financial_type_id'
- ),
- array(
- 'table' => 'civicrm_pledge',
- 'column' => 'financial_type_id',
- ),
- );
-
- $errors = array();
- $params = array( 1 => array($financialTypeId, 'Integer'));
- if (CRM_Core_DAO::doesValueExistInTable( $tables, $params, $errors)) {
- $message = ts('The following tables have an entry for this financial type') . ': ';
- $message .= implode( ', ', array_keys($errors));
+ $financialType = new CRM_Financial_DAO_FinancialType( );
+ $financialType->id = $financialTypeId;
+ $financialType->find(true);
+ //TODO: if (!$financialType->find(true)) {
+
+ // ensure that we have no objects that have an FK to this financial type id TODO: that cannot be null
+ $occurrences = $financialType->findReferences();
+ if ($occurrences) {
+ $tables = array();
+ foreach ($occurrences as $occurence) {
+ $tables[] = get_class($occurence);
+ }
+ $message = ts('The following tables have an entry for this financial type: %1', array( '%1' => implode(', ', $tables) ));
$errors = array();
$errors['is_error'] = 1;
}
//delete from financial Type table
- $financialType = new CRM_Financial_DAO_FinancialType( );
- $financialType->id = $financialTypeId;
$financialType->delete();
$entityFinancialType = new CRM_Financial_DAO_EntityFinancialAccount( );
$entityFinancialType->entity_id = $financialTypeId;
$entityFinancialType->entity_table = 'civicrm_financial_type';
- $entityFinancialType ->delete();
+ $entityFinancialType->delete();
return FALSE;
}
}
function _civicrm_api_get_camel_name($entity, $version = NULL) {
- static $_map = NULL;
-
if (empty($version)) {
$version = civicrm_get_api_version();
}
- if (isset($_map[$version][strtolower($entity)])) {
- return $_map[$version][strtolower($entity)];
- }
-
$fragments = explode('_', $entity);
foreach ($fragments as & $fragment) {
$fragment = ucfirst($fragment);
--- /dev/null
+<?php
+
+require_once 'CiviTest/CiviUnitTestCase.php';
+class CRM_Core_DAOTest extends CiviUnitTestCase {
+ function get_info() {
+ return array(
+ 'name' => 'DAO',
+ 'description' => 'Test core DAO functions',
+ 'group' => 'Core',
+ );
+ }
+
+ function testGetReferenceColumns() {
+ // choose CRM_Core_DAO_Email as an arbitrary example
+ $emailRefs = CRM_Core_DAO_Email::getReferenceColumns();
+ $refsByTarget = array();
+ foreach ($emailRefs as $refSpec) {
+ $refsByTarget[$refSpec->getTargetTable()] = $refSpec;
+ }
+ $this->assertTrue(array_key_exists('civicrm_contact', $refsByTarget));
+ $contactRef = $refsByTarget['civicrm_contact'];
+ $this->assertEquals('contact_id', $contactRef->getReferenceKey());
+ $this->assertEquals('id', $contactRef->getTargetKey());
+ $this->assertEquals(FALSE, $contactRef->isGeneric());
+ }
+
+ function testGetReferencesToTable() {
+ $refs = CRM_Core_DAO::getReferencesToTable(CRM_Financial_DAO_FinancialType::getTableName());
+ $refsBySource = array();
+ foreach ($refs as $refSpec) {
+ $refsBySource[$refSpec->getReferenceTable()] = $refSpec;
+ }
+ $this->assertTrue(array_key_exists('civicrm_entity_financial_account', $refsBySource));
+ $genericRef = $refsBySource['civicrm_entity_financial_account'];
+ $this->assertEquals('entity_id', $genericRef->getReferenceKey());
+ $this->assertEquals('entity_table', $genericRef->getTypeColumn());
+ $this->assertEquals('id', $genericRef->getTargetKey());
+ $this->assertEquals(TRUE, $genericRef->isGeneric());
+ }
+
+ function testFindReferences() {
+ $params = array(
+ 'first_name' => 'Testy',
+ 'last_name' => 'McScallion',
+ 'contact_type' => 'Individual',
+ );
+
+ $contact = CRM_Contact_BAO_Contact::add($params);
+ $this->assertNotNull($contact->id);
+
+ $params = array(
+ 'email' => 'spam@dev.null',
+ 'contact_id' => $contact->id,
+ 'is_primary' => 0,
+ 'location_type_id' => 1,
+ );
+
+ $email = CRM_Core_BAO_Email::add($params);
+
+ $refs = $contact->findReferences();
+ $refsByTable = array();
+ foreach ($refs as $refObj) {
+ $refsByTable[$refObj->__table] = $refObj;
+ }
+
+ $this->assertTrue(array_key_exists('civicrm_email', $refsByTable));
+ $refDao = $refsByTable['civicrm_email'];
+ $refDao->find(TRUE);
+ $this->assertEquals($contact->id, $refDao->contact_id);
+ }
+}
$table['foreignKey'] = &$foreign;
}
+ if ($this->value('dynamicForeignKey', $tableXML)) {
+ $dynamicForeign = array();
+ foreach ($tableXML->dynamicForeignKey as $foreignXML) {
+ if ($this->value('drop', $foreignXML, 0) > 0 and $this->value('drop', $foreignXML, 0) <= $this->buildVersion) {
+ continue;
+ }
+ if ($this->value('add', $foreignXML, 0) <= $this->buildVersion) {
+ $this->getDynamicForeignKey($foreignXML, $dynamicForeign, $name);
+ }
+ }
+ $table['dynamicForeignKey'] = $dynamicForeign;
+ }
+
$tables[$name] = &$table;
return;
}
$foreignKeys[$name] = &$foreignKey;
}
+ function getDynamicForeignKey(&$foreignXML, &$dynamicForeignKeys) {
+ $foreignKey = array(
+ 'idColumn' => trim($foreignXML->idColumn),
+ 'typeColumn' => trim($foreignXML->typeColumn),
+ 'key' => trim($this->value('key', $foreignXML)),
+ );
+ $dynamicForeignKeys[] = $foreignKey;
+ }
+
protected function value($key, &$object, $default = NULL) {
if (isset($object->$key)) {
return (string ) $object->$key;
<comment>ID of the object possessing this ACL</comment>
<add>1.6</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>1.6</add>
+ </dynamicForeignKey>
<field>
<name>operation</name>
<type>enum</type>
<comment>ID of the group/contact object being joined</comment>
<add>1.6</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>1.6</add>
+ </dynamicForeignKey>
<field>
<name>is_active</name>
<type>boolean</type>
<drop>2.0</drop>
</field>
+ <dynamicForeignKey>
+ <idColumn>activity_entity_id</idColumn>
+ <typeColumn>activity_entity_table</typeColumn>
+ <add>1.8</add>
+ <drop>2.0</drop>
+ </dynamicForeignKey>
+
<field>
<name>target_entity_table</name>
<type>varchar</type>
<drop>2.0</drop>
</field>
+ <dynamicForeignKey>
+ <idColumn>target_entity_id</idColumn>
+ <typeColumn>target_entity_table</typeColumn>
+ <add>1.8</add>
+ <drop>2.0</drop>
+ </dynamicForeignKey>
+
<field>
<name>activity_id</name>
<type>int unsigned</type>
<comment>FK to entity table specified in entity_table column.</comment>
<add>3.3</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>3.3</add>
+ </dynamicForeignKey>
<index>
<name>index_entity</name>
<fieldName>entity_table</fieldName>
<comment>Entity id of referenced table.</comment>
<add>3.3</add>
</field>
+
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>3.3</add>
+ </dynamicForeignKey>
</table>
<drop>2.0</drop>
</field>
+ <dynamicForeignKey>
+ <idColumn>activity_entity_id</idColumn>
+ <typeColumn>activity_entity_table</typeColumn>
+ <add>1.8</add>
+ <drop>2.0</drop>
+ </dynamicForeignKey>
</table>
<required>true</required>
<add>1.4</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>1.4</add>
+ </dynamicForeignKey>
<field>
<name>premiums_active </name>
<type>boolean</type>
<comment>name of the entity table for the above id, e.g. civicrm_activity, civicrm_participant</comment>
<add>3.4</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>3.4</add>
+ </dynamicForeignKey>
<field>
<name>action_schedule_id</name>
<type>int unsigned</type>
<comment>FK to entity table specified in entity_table column.</comment>
<add>2.1</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>2.1</add>
+ </dynamicForeignKey>
<index>
<name>index_entity</name>
<fieldName>entity_table</fieldName>
<add>4.3</add>
<onDelete>CASCADE</onDelete>
</foreignKey>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ </dynamicForeignKey>
<index>
<name>index_entity_option_id</name>
<fieldName>entity_table</fieldName>
<type>int unsigned</type>
<required>true</required>
<comment>FK to entity table specified in entity_table column.</comment>
- <add>1.5</add>
+ <add>1.5</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>1.5</add>
+ </dynamicForeignKey>
<index>
<name>index_entity</name>
<fieldName>entity_table</fieldName>
<title>Entity ID</title>
<required>true</required>
<comment>FK to entity table specified in entity_table column.</comment>
- <add>3.2</add>
+ <add>3.2</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>3.2</add>
+ </dynamicForeignKey>
<index>
<name>index_entity</name>
<fieldName>entity_table</fieldName>
<comment>Foreign key to the referenced item.</comment>
<add>1.5</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>1.5</add>
+ </dynamicForeignKey>
<index>
<name>index_entity</name>
<add>1.5</add>
<comment>Foreign key to the referenced item.</comment>
<add>1.1</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>1.1</add>
+ </dynamicForeignKey>
<index>
<name>index_entity</name>
<fieldName>entity_table</fieldName>
<comment>Foreign key to the referenced item.</comment>
<add>1.3</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>1.3</add>
+ </dynamicForeignKey>
<index>
<name>index_entity</name>
<add>1.3</add>
<add>4.3</add>
<comment>Links to an id in the entity_table, such as vid in civicrm_financial_type</comment>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>4.3</add>
+ </dynamicForeignKey>
<field>
<name>account_relationship</name>
<type>int unsigned</type>
<required>true</required>
<add>3.2</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>3.2</add>
+ </dynamicForeignKey>
<field>
<name>financial_trxn_id</name>
<type>int unsigned</type>
<comment>The specific source item that is responsible for the creation of this financial_item</comment>
<add>4.3</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>4.3</add>
+ </dynamicForeignKey>
<index>
<name>UI_id</name>
<fieldName>id</fieldName>
<required>true</required>
<comment>Foreign key to the referenced item.</comment>
<add>2.0</add>
- </field>
- <field>
+ </field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>2.0</add>
+ </dynamicForeignKey>
+ <field>
<name>title</name>
<type>varchar</type>
<length>255</length>
<required>true</required>
<comment>Foreign key to the referenced item.</comment>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ </dynamicForeignKey>
<field>
<name>search_id</name>
<type>int</type>
<comment>FK to civicrm_contribution_page.id OR civicrm_event.id</comment>
<add>2.2</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>2.2</add>
+ </dynamicForeignKey>
<foreignKey>
<name>entity_id</name>
<table>civicrm_contribution_page</table>
<comment>The entity that this pcp targets</comment>
<add>4.1</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>target_entity_id</idColumn>
+ <!-- FIXME: typename and not tablename? -->
+ <typeColumn>target_entity_type</typeColumn>
+ <add>4.1</add>
+ </dynamicForeignKey>
<field>
<name>supporter_profile_id</name>
<type>int unsigned</type>
<type>int unsigned</type>
<required>true</required>
<comment>FK to entity table specified in entity_table column.</comment>
- <add>2.1</add>
+ <add>2.1</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>2.1</add>
+ </dynamicForeignKey>
<index>
<name>index_entity</name>
<fieldName>entity_table</fieldName>
<comment>entry in table</comment>
<add>1.7</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>1.7</add>
+ </dynamicForeignKey>
<field>
<name>price_field_id</name>
<type>int unsigned</type>
<comment>Item in table</comment>
<add>1.8</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>entity_id</idColumn>
+ <typeColumn>entity_table</typeColumn>
+ <add>1.8</add>
+ </dynamicForeignKey>
<field>
<name>price_set_id</name>
<type>int unsigned</type>
<comment>Foreign key to project owner (contact, group, etc.).</comment>
<add>1.5</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>owner_entity_id</idColumn>
+ <typeColumn>owner_entity_table</typeColumn>
+ <add>1.5</add>
+ </dynamicForeignKey>
<field>
<name>start_date</name>
<title>Start Date</title>
<comment>Foreign key to Task owner (contact, group, etc.).</comment>
<add>1.5</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>owner_entity_id</idColumn>
+ <typeColumn>owner_entity_table</typeColumn>
+ <add>1.5</add>
+ </dynamicForeignKey>
<field>
<name>parent_entity_table</name>
<type>varchar</type>
<comment>Optional foreign key to Task Parent (project, another task, etc.).</comment>
<add>1.5</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>parent_entity_id</idColumn>
+ <typeColumn>parent_entity_table</typeColumn>
+ <add>1.5</add>
+ </dynamicForeignKey>
<field>
<name>due_date</name>
<title>Due Date</title>
<comment>Foreign key to responsible entity (contact, group, etc.).</comment>
<add>1.5</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>responsible_entity_id</idColumn>
+ <typeColumn>responsible_entity_table</typeColumn>
+ <add>1.5</add>
+ </dynamicForeignKey>
<field>
<name>target_entity_table</name>
<type>varchar</type>
<comment>Foreign key to target entity (contact, group, etc.).</comment>
<add>1.5</add>
</field>
+ <dynamicForeignKey>
+ <idColumn>target_entity_id</idColumn>
+ <typeColumn>target_entity_table</typeColumn>
+ <add>1.5</add>
+ </dynamicForeignKey>
<field>
<name>status_detail</name>
<title>Status Details</title>
parent::__construct( );
{rdelim}
-{if $table.foreignKey}
+{if $table.foreignKey || $table.dynamicForeignKey}
/**
- * return foreign links
+ * return foreign keys and entity references
*
+ * @static
* @access public
- * @return array
+ * @return array of CRM_Core_EntityReference
*/
- function links( ) {ldelim}
- if ( ! ( self::$_links ) ) {ldelim}
- self::$_links = array(
+ static function getReferenceColumns() {ldelim}
+ if (!self::$_links) {ldelim}
+ self::$_links = array(
{foreach from=$table.foreignKey item=foreign}
- '{$foreign.name}' => '{$foreign.table}:{$foreign.key}',
+ new CRM_Core_EntityReference(self::getTableName(), '{$foreign.name}', '{$foreign.table}', '{$foreign.key}'),
{/foreach}
- );
- {rdelim}
- return self::$_links;
+
+{foreach from=$table.dynamicForeignKey item=foreign}
+ new CRM_Core_EntityReference(self::getTableName(), '{$foreign.idColumn}', NULL, '{$foreign.key|default:'id'}', '{$foreign.typeColumn}'),
+{/foreach}
+ );
+ {rdelim}
+ return self::$_links;
{rdelim}
{/if} {* table.foreignKey *}