diff --git a/tripal/src/Services/TripalPublish.php b/tripal/src/Services/TripalPublish.php
index c596721855..980fc91690 100644
--- a/tripal/src/Services/TripalPublish.php
+++ b/tripal/src/Services/TripalPublish.php
@@ -476,12 +476,20 @@ protected function getEntityTitles($matches) {
$delta = 0;
$field = $this->field_info[$field_name]['instance'];
$main_prop = $field->mainPropertyName();
- $value = $match[$field_name][$delta][$main_prop]['value']->getValue();
- if ($value !== NULL) {
- $entity_title = trim(preg_replace("/\[$field_name\]/", $value, $entity_title));
+ $value = '';
+ if (array_key_exists($delta, $match[$field_name])) {
+ $value = $match[$field_name][$delta][$main_prop]['value']->getValue();
}
+ if ($value === NULL) {
+ $value = '';
+ }
+ $entity_title = trim(preg_replace("/\[$field_name\]/", $value, $entity_title));
}
}
+ // Trim any trailing spaces and remove double spaces. Double spaces
+ // can occur if a token replacement has no value but there are spaces
+ // around it.
+ $entity_title = trim(preg_replace('/\s\s+/', ' ', $entity_title));
$titles[] = $entity_title;
}
return $titles;
@@ -638,7 +646,7 @@ protected function findFieldItems($field_name, $entities) {
$items = [];
$sql = "
- SELECT entity_id FROM {" . $field_table . "}\n
+ SELECT entity_id, delta FROM {" . $field_table . "}\n
WHERE bundle = :bundle\n
AND entity_id IN (:entity_ids[])\n";
@@ -660,7 +668,11 @@ protected function findFieldItems($field_name, $entities) {
];
$results = $database->query($sql, $args);
while ($result = $results->fetchAssoc()) {
- $items[$result['entity_id']] = $result['entity_id'];
+ $entity_id = $result['entity_id'];
+ if (!array_key_exists($entity_id, $items)) {
+ $items[$entity_id] = [];
+ }
+ $items[$entity_id][$result['delta']] = TRUE;
}
$this->setItemsHandled($batch_num);
$batch_num++;
@@ -710,8 +722,11 @@ protected function countFieldMatches(string $field_name, array $matches) : int {
* An associative array that maps entity titles to their keys.
* @param array $existing
* An associative array of entities that already have an existing item for this field.
+ *
+ * @return int
+ * The number of items inserted for the field.
*/
- protected function insertFieldItems($field_name, $matches, $titles, $entities, $existing) {
+ protected function insertFieldItems($field_name, $matches, $titles, $entities, $existing, &$published) {
$database = \Drupal::database();
$field_table = 'tripal_entity__' . $field_name;
@@ -733,49 +748,60 @@ protected function insertFieldItems($field_name, $matches, $titles, $entities, $
$init_sql = rtrim($init_sql, ", ");
$init_sql .= ") VALUES\n";
+ $i = 0;
$j = 0;
$total = 0;
$batch_num = 1;
$sql = '';
$args = [];
+ $num_inserted = 0;
+
- // Iterate through the matches.
+ // Iterate through the matches. Each match corresponds to a single
+ // entity. The titles provided should be in order of the entities
+ // in the matches array.
foreach ($matches as $match) {
- $title = $titles[$total];
+
+ $title = $titles[$i];
$entity_id = $entities[$title];
+ $i++;
- $num_delta = count(array_keys($match[$field_name]));
- for ($delta = 0; $delta < $num_delta; $delta++) {
+ // Iterate through the "items" of each feild and insert a record value
+ // for each item.
+ $num_items = count(array_keys($match[$field_name]));
+ for ($delta = 0; $delta < $num_items; $delta++) {
$j++;
$total++;
// No need to add items to those that are already published.
- if (array_key_exists($entity_id, $existing)) {
- continue;
- }
-
- // Add items to those that are not already published.
- $sql .= "(:bundle_$j, :deleted_$j, :entity_id_$j, :revision_id_$j, :langcode_$j, :delta_$j, ";
- $args[":bundle_$j"] = $this->bundle;
- $args[":deleted_$j"] = 0;
- $args[":entity_id_$j"] = $entity_id;
- $args[":revision_id_$j"] = 1;
- $args[":langcode_$j"] = 'und';
- $args[":delta_$j"] = $delta;
- foreach (array_keys($this->required_types[$field_name]) as $key) {
- $placeholder = ':' . $field_name . '_'. $key . '_' . $j;
- $sql .= $placeholder . ', ';
- $args[$placeholder] = $match[$field_name][$delta][$key]['value']->getValue();
+ if (!array_key_exists($entity_id, $existing) or
+ !array_key_exists($delta, $existing[$entity_id])) {
+
+ $published[$entity_id] = $title;
+
+ // Add items to those that are not already published.
+ $sql .= "(:bundle_$j, :deleted_$j, :entity_id_$j, :revision_id_$j, :langcode_$j, :delta_$j, ";
+ $args[":bundle_$j"] = $this->bundle;
+ $args[":deleted_$j"] = 0;
+ $args[":entity_id_$j"] = $entity_id;
+ $args[":revision_id_$j"] = 1;
+ $args[":langcode_$j"] = 'und';
+ $args[":delta_$j"] = $delta;
+ foreach (array_keys($this->required_types[$field_name]) as $key) {
+ $placeholder = ':' . $field_name . '_'. $key . '_' . $j;
+ $sql .= $placeholder . ', ';
+ $args[$placeholder] = $match[$field_name][$delta][$key]['value']->getValue();
+ }
+ $sql = rtrim($sql, ", ");
+ $sql .= "),\n";
+ $num_inserted++;
}
- $sql = rtrim($sql, ", ");
- $sql .= "),\n";
// If we've reached the size of the batch then let's do the insert.
if ($j == $batch_size or $total == $num_matches) {
if (count($args) > 0) {
$sql = rtrim($sql, ",\n");
$sql = $init_sql . $sql;
-
$database->query($sql, $args);
}
$this->setItemsHandled($batch_num);
@@ -788,6 +814,7 @@ protected function insertFieldItems($field_name, $matches, $titles, $entities, $
}
}
}
+ return $num_inserted;
}
/**
@@ -879,18 +906,21 @@ public function publish($filters = []) {
}
$total_items = 0;
+ $published_entities = [];
foreach ($this->field_info as $field_name => $field_info) {
$this->logger->notice(" Checking for published items for the field: $field_name...");
$existing_field_items = $this->findFieldItems($field_name, $entities);
- $num_field_items = $this->countFieldMatches($field_name, $matches);
- $this->logger->notice(" Publishing " . number_format($num_field_items) . " items for field: $field_name...");
- $this->insertFieldItems($field_name, $matches, $titles, $entities, $existing_field_items);
- $total_items += $num_field_items;
+ $num_inserted = $this->insertFieldItems($field_name, $matches, $titles,
+ $entities, $existing_field_items, $published_entities);
+
+ $this->logger->notice(" Published " . number_format($num_inserted) . " items for field: $field_name...");
+ $total_items += $num_inserted;
}
- $this->logger->notice("Published " . number_format(count($new_matches)) . " new entities, and " . number_format($total_items) . " field values.");
+ $this->logger->notice("Published " . number_format(count(array_keys($published_entities)))
+ . " new entities, and " . number_format($total_items) . " field values.");
$this->logger->notice('Done');
- return $entities;
+ return $published_entities;
}
}
diff --git a/tripal/src/TripalStorage/TripalStorageBase.php b/tripal/src/TripalStorage/TripalStorageBase.php
index f5117c8cbb..b53af3e8b4 100644
--- a/tripal/src/TripalStorage/TripalStorageBase.php
+++ b/tripal/src/TripalStorage/TripalStorageBase.php
@@ -228,20 +228,34 @@ protected function cloneValues($values) {
}
/**
- * A helper function to add a new item for a field by cloning delta 0.
+ * Sets the values for a field to be empty.
+ *
+ * If the delta value doesn't exist in the values array then a new values
+ * array is added.
*
* @param array $values
* An array of property values.
* @param string $field_name
* The name of the field to addd an item to.
*/
- protected function addEmptyValuesItem(&$values, $field_name) {
+ protected function resetValuesItem(&$values, $field_name, $delta) {
+ $is_new = FALSE;
+
+ // Is the caller wanting to add a new element? If so, add one.
$num_items = count($values[$field_name]);
- $values[$field_name][$num_items] = [];
- foreach ($values[$field_name][0] as $key => $value) {
- $values[$field_name][$num_items][$key] = [];
- $values[$field_name][$num_items][$key]['value'] = clone $value['value'];
- $values[$field_name][$num_items][$key]['value']->setValue(NULL);
+ if ($delta > $num_items - 1) {
+ $values[$field_name][$delta] = [];
+ $is_new = TRUE;
+ }
+
+ // Reset the values to NULL. Use the first values element
+ // to get the keys.
+ foreach ($values[$field_name][0] as $key => $details) {
+ if ($is_new) {
+ $values[$field_name][$delta][$key] = [];
+ $values[$field_name][$delta][$key]['value'] = clone $details['value'];
+ }
+ $values[$field_name][$delta][$key]['value']->setValue(NULL);
}
}
diff --git a/tripal/src/TripalVocabTerms/PluginManagers/TripalCollectionPluginManager.php b/tripal/src/TripalVocabTerms/PluginManagers/TripalCollectionPluginManager.php
index 30c2603876..2533d09d53 100644
--- a/tripal/src/TripalVocabTerms/PluginManagers/TripalCollectionPluginManager.php
+++ b/tripal/src/TripalVocabTerms/PluginManagers/TripalCollectionPluginManager.php
@@ -148,7 +148,7 @@ public function getCollectionList() {
* @param string $name
* The name.
*
- * @return Drupal\tripal\TripalVocabTerms\TripalCollectionPluginBase|NULL
+ * @return \Drupal\tripal\TripalVocabTerms\TripalCollectionPluginBase|NULL
* The loaded collection plugin or NULL.
*/
public function loadCollection($name) {
diff --git a/tripal_chado/src/Plugin/Field/FieldType/ChadoContactTypeDefault.php b/tripal_chado/src/Plugin/Field/FieldType/ChadoContactTypeDefault.php
index 4f9954965d..1d7fdc4137 100644
--- a/tripal_chado/src/Plugin/Field/FieldType/ChadoContactTypeDefault.php
+++ b/tripal_chado/src/Plugin/Field/FieldType/ChadoContactTypeDefault.php
@@ -161,6 +161,7 @@ public static function tripalTypes($field_definition) {
// An intermediate linker table is used
else {
// Define the linker table that links the base table to the object table.
+ // E.g.: project_contact.project_contact_id
$properties[] = new ChadoIntStoragePropertyType($entity_type_id, self::$id, 'linker_id', self::$record_id_term, [
'action' => 'store_pkey',
'drupal_store' => TRUE,
@@ -168,6 +169,7 @@ public static function tripalTypes($field_definition) {
]);
// Define the link between the base table and the linker table.
+ // E.g.: project.project_id>project_contact.project_id
$properties[] = new ChadoIntStoragePropertyType($entity_type_id, self::$id, 'link', $linker_left_term, [
'action' => 'store_link',
'drupal_store' => TRUE,
@@ -175,6 +177,7 @@ public static function tripalTypes($field_definition) {
]);
// Define the link between the linker table and the object table.
+ // E.g.: project_contact.contact_id
$properties[] = new ChadoIntStoragePropertyType($entity_type_id, self::$id, $linker_fkey_column, $linker_fkey_term, [
'action' => 'store',
'drupal_store' => TRUE,
diff --git a/tripal_chado/src/Plugin/Field/FieldType/ChadoOrganismTypeDefault.php b/tripal_chado/src/Plugin/Field/FieldType/ChadoOrganismTypeDefault.php
index 70a174c780..c8008c9356 100644
--- a/tripal_chado/src/Plugin/Field/FieldType/ChadoOrganismTypeDefault.php
+++ b/tripal_chado/src/Plugin/Field/FieldType/ChadoOrganismTypeDefault.php
@@ -105,7 +105,7 @@ public static function tripalTypes($field_definition) {
// Cvterm table, to retrieve the name for the organism type
$cvterm_schema_def = $chado->schema()->getTableDef('cvterm', ['format' => 'Drupal']);
- $infraspecific_type_term = $mapping->getColumnTermId('cvterm', 'name');
+ $infraspecific_type_term = $mapping->getColumnTermId('organism', 'type_id');
$infraspecific_type_len = $cvterm_schema_def['fields']['name']['size'];
// Scientific name is built from several fields combined with space characters
diff --git a/tripal_chado/src/Plugin/Field/FieldWidget/ChadoAdditionalTypeWidgetDefault.php b/tripal_chado/src/Plugin/Field/FieldWidget/ChadoAdditionalTypeWidgetDefault.php
index 373b19d1a0..95a8b8a7fc 100644
--- a/tripal_chado/src/Plugin/Field/FieldWidget/ChadoAdditionalTypeWidgetDefault.php
+++ b/tripal_chado/src/Plugin/Field/FieldWidget/ChadoAdditionalTypeWidgetDefault.php
@@ -139,6 +139,7 @@ public function massageFormValues(array $values, array $form, FormStateInterface
$idSpace_manager = \Drupal::service('tripal.collection_plugin_manager.idspace');
foreach ($values as $delta => $item) {
$matches = [];
+ $values[$delta]['type_id'] = NULL;
if (preg_match('/(.+?)\(([^\(]+?):(.+?)\)/', $item['term_autoc'], $matches)) {
$termIdSpace = $matches[2];
$termAccession = $matches[3];
diff --git a/tripal_chado/src/Plugin/TripalStorage/ChadoStorage.php b/tripal_chado/src/Plugin/TripalStorage/ChadoStorage.php
index 90484cc9d0..ba20592e1e 100644
--- a/tripal_chado/src/Plugin/TripalStorage/ChadoStorage.php
+++ b/tripal_chado/src/Plugin/TripalStorage/ChadoStorage.php
@@ -255,12 +255,12 @@ public function loadValues(&$values) : bool {
foreach ($base_tables as $base_table) {
// Do the select for the base tables
- $this->records->selectRecords($base_table, $base_table);
+ $this->records->selectItems($base_table, $base_table);
// Then do the selects for the ancillary tables.
$tables = $this->records->getAncillaryTables($base_table);
foreach ($tables as $table_alias) {
- $this->records->selectRecords($base_table, $table_alias);
+ $this->records->selectItems($base_table, $table_alias);
}
}
$this->setPropValues($values, $this->records);
@@ -314,52 +314,51 @@ public function findValues($values) {
foreach ($base_tables as $base_table) {
// First we find all matching base records.
- $matches = $this->records->findRecords($base_table, $base_table);
+ $entity_matches = $this->records->findRecords($base_table, $base_table);
// Now for each matching base record we need to select
// the ancillary tables.
- foreach ($matches as $match) {
+ foreach ($entity_matches as $match) {
// Clone the value array for this match.
$new_values = $this->cloneValues($values);
- // Iterate through tables that have conditions. We don't want to
- // query tables that only have a condition with a link to the base
- // table because these records aren't providing any filters to limit
- // the base records.
+ // Limit base records by iterating through tables with conditions.
$tables = $this->records->getAncillaryTablesWithCond($base_table);
- $found_match = TRUE;
foreach ($tables as $table_alias) {
- $num_found = $match->selectRecords($base_table, $table_alias);
-
- // In order for a set of records to be considered found it must
- // match all criteria, which means all ancillary tables must
- // return results.
- // @todo: when we need more fancy querying where we can set
- // "or" clauses then this will need to be adjust. For now, we
- // only use findValues() for publishing and in this case all
- // criteria must be met.
- if ($num_found == 0) {
- $found_match = FALSE;
+
+ // Now find any items for this linked table.
+ $num_items_found = $match->selectItems($base_table, $table_alias);
+ if ($num_items_found == 0) {
continue;
}
- // Add any additional items to the values array that are needed.
- $num_items = $match->getNumTableItems($base_table, $table_alias);
- for ($i = 0; $i < $num_items - 1; $i++) {
- $table_fields = $match->getTableFields($base_table, $table_alias);
- foreach ($table_fields as $field_name) {
- $this->addEmptyValuesItem($new_values, $field_name);
+ // Prepare the values array to receive all the new values. We'll
+ // get all the fields for this ancillary table and then
+ // reset the values in the new cloned values array for all of
+ // those fields.
+ $table_fields = $match->getTableFields($base_table, $table_alias);
+ foreach ($table_fields as $field_name) {
+ for ($i = 0; $i < $num_items_found; $i++) {
+ $this->resetValuesItem($new_values, $field_name, $i);
}
}
}
// Now set the values.
- if ($found_match) {
- $this->setPropValues($new_values, $match);
- $found_list[] = $new_values;
+ $this->setPropValues($new_values, $match);
+
+ // Remove any values that are not valid.
+ foreach ($new_values as $field_name => $deltas) {
+ foreach ($deltas as $delta => $properties) {
+ $is_valid = $this->isFieldValid($field_name, $delta, $new_values);
+ if (!$is_valid) {
+ unset($new_values[$field_name][$delta]);
+ }
+ }
}
+ $found_list[] = $new_values;
}
}
}
@@ -440,22 +439,25 @@ protected function setPropValues(&$values, ChadoRecords $records) {
$column_alias = $value_col_info['column_alias'];
// For values that come from joins, we need to use the root table
- // becuase this is the table that will have the value.
- $my_delta = $delta;
- if($action == 'read_value' and array_key_exists('join', $path_array)) {
+ // because this is the table that will have the value.
+ if ($action == 'read_value' and array_key_exists('join', $path_array)) {
$root_alias = $value_col_info['root_alias'];
$table_alias = $root_alias;
}
- // Anytime we need to pull data from the base table, the delta
+ // Anytime we need to pull a value from the base table, the delta
// should always be zero. There will only ever be one base record.
+ // This is needed because all fields use a `record_id` which has
+ // a path that is set for the base table.
+ $value_delta = $delta;
if ($table_alias == $base_table) {
- $my_delta = 0;
+ $value_delta = 0;
}
- // Set the value.
- $value = $records->getColumnValue($base_table, $table_alias, $my_delta, $column_alias);
- $values[$field_name][$delta][$key]['value']->setValue($value);
+ $value = $records->getColumnValue($base_table, $table_alias, $value_delta, $column_alias);
+ if ($value !== NULL) {
+ $values[$field_name][$delta][$key]['value']->setValue($value);
+ }
}
}
}
@@ -526,6 +528,55 @@ protected function setPropValues(&$values, ChadoRecords $records) {
}
}
+
+ /**
+ * Checks if a field has all necessary elements to be considered 'found'.
+ *
+ * The ChadoRecords class will search for all records necessary to
+ * populate the values of the fields for a content type. This works well
+ * when all conditions are set for the insertValues() and loadValues().
+ * However, for the findValues() function there are often no criteria set
+ * and we want to find all linked records associated with a base record.
+ * All Chado fields will have a `record_id` property and the value of that
+ * comes from the base table. This means that all fields will have at
+ * least one property set even if nothing was found. So we need to know
+ * if the field has a valid set of property values. If so, we can
+ * proceed as if the field was "found" otherwise, we should remove the
+ * field values as nothing was found.
+ *
+ * A field is valid if all of the properties that have an action of 'store'
+ * have a non NULL value and if all required properties have a non NULL value.
+ *
+ * @param string $field_name
+ * The name of the field.
+ * @param integer $delta
+ * The field item's delta value.
+ * @param array $values
+ * An array of field values.
+ * @return boolean
+ * returns TRUE if the field has all necessary elements for inserting
+ * into the Drupal tables for publishing. FALSE otherwise.
+ */
+ protected function isFieldValid($field_name, $delta, $values) {
+
+ foreach ($values[$field_name][$delta] as $key => $prop_value) {
+ /** @var \Drupal\tripal\TripalStorage\StoragePropertyTypeBase $prop_type **/
+ $prop_type = $this->getPropertyType($field_name, $key);
+ $prop_settings = $prop_type->getStorageSettings();
+ $action = $prop_settings['action'];
+ $is_store = preg_match('/^store/', $action);
+ $value = $prop_value['value']->getValue();
+ $is_required = $prop_type->getRequired();
+ if ($is_store and $value === NULL) {
+ return FALSE;
+ }
+ if ($is_required and $value === NULL) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+
/**
* Indexes a values array for easy lookup.
*
@@ -930,6 +981,10 @@ protected function handleReadValue(array $context, StoragePropertyValue $prop_va
/**
* Takes a path string for a field property and converts it to an array structure.
*
+ * @param string $field_name
+ * The name of the field.
+ * @param string $base_table
+ * The name of the base table for thie field.
* @param mixed $path
* A string continaining the path. Note: this is a recursive function and on
* recursive calls this variable will be n array. Hence, the type is "mixed".*
@@ -946,7 +1001,8 @@ protected function handleReadValue(array $context, StoragePropertyValue $prop_va
* @return array
*
*/
- protected function parsePath(string $field_name, string $base_table, mixed $path, array $aliases = [], string $as = '', string $full_path = '') {
+ protected function parsePath(string $field_name, string $base_table, mixed $path,
+ array $aliases = [], string $as = '', string $full_path = '') {
// If the path is a string then split it.
$path_arr = [];
@@ -1020,7 +1076,7 @@ protected function parsePath(string $field_name, string $base_table, mixed $path
$sub_path_arr = $this->parsePath($field_name, $base_table, $path_arr, $aliases, $as, $full_path);
}
// If there are no more joins, then we need to set the value column to be
- // the same as the last column in tge join.
+ // the same as the last column in the join.
else {
$ret_array['join']['value_column'] = $right_column;
$ret_array['join']['value_alias'] = $as ? $as : $right_column;
@@ -1051,7 +1107,6 @@ protected function parsePath(string $field_name, string $base_table, mixed $path
if (array_key_exists($table_alias, $aliases)) {
$chado_table = $aliases[$table_alias];
}
-
// If the base table is not the same as the root table then
// we should add the field name to the colun alias. Otherwise
// we may have conflicts if mutiple fields use the same alias.
@@ -1072,7 +1127,8 @@ protected function parsePath(string $field_name, string $base_table, mixed $path
}
// There is no period in the path so there is no Chado table. We are at the
- // end of the path with joins and we can just return the value column.
+ // end of the path with joins and the value column is not the same as the
+ // right join column. We can just return the value column.
else {
// If the base table is not the same as the root table then
// we should add the field name to the colun alias. Otherwise
@@ -1255,9 +1311,8 @@ static public function drupalEntityIdLookupCallback($context) {
}
$record_id = $record_id->getValue('value');
- // During publish, record_id may be null. In this particular case, return null.
if (!$record_id) {
- return NULL;
+ return -1;
}
// Given the Chado record ID and bundle term, we can lookup the Drupal entity ID.
diff --git a/tripal_chado/src/TripalStorage/ChadoRecords.php b/tripal_chado/src/TripalStorage/ChadoRecords.php
index ec3a0ab852..89782ef73c 100644
--- a/tripal_chado/src/TripalStorage/ChadoRecords.php
+++ b/tripal_chado/src/TripalStorage/ChadoRecords.php
@@ -187,7 +187,9 @@ protected function initTable($elements) : bool {
// for the base table that should be included in the record.
'columns' => [],
- // An array the indicates which fields want column values.
+ // An array mapping which Tripal fields want which Chado column values.
+ // The key is the column alias and the value is an array, one entry
+ // for each field/property that uses the value.
'field_columns' => [],
// Conditinos for this table when performing a query.
@@ -216,7 +218,14 @@ protected function initTable($elements) : bool {
// any columns from joined tables. There is no guarnatee that fields
// won't give the same name to the same fields in the same tables so
// these values will be indexed by the field and key they belong to.
- 'values' => []
+ 'values' => [],
+
+ // A boolean to indicate if any values have been set. We can't
+ // rely on checking if all values are empty because it could be
+ // possible that all values are meant to be empty. This value will
+ // get set when a query is successful for the table and values have
+ // been set.
+ 'has_values' => FALSE,
];
}
return TRUE;
@@ -687,9 +696,9 @@ public function setLinks(string $base_table) {
// if this column is an ID field and links to this base table then update the value.
if (array_key_exists($column_alias, $record['link_columns'])) {
- $base_table = $record['link_columns'][$column_alias];
- $record_id = $record_id;
- $this->setColumnValue($base_table, $table_alias, $delta, $column_alias, $record_id);
+ $base_table = $record['link_columns'][$column_alias];
+ $this->records[$base_table]['tables'][$table_alias]['items'][$delta]['values'][$column_alias] = $record_id;
+
// If a condition exists for this id set it as well.
if (array_key_exists($column_alias, $record['conditions'])) {
@@ -824,11 +833,6 @@ public function getAncillaryTables(string $base_table) {
/**
* For the given base table, returns non base tables that have conditions set.
*
- * Excludes tables whose only condition is the linker column to the base
- * table. This function is useful when finding values. We don't want
- * to iterate through tables that won't have any records to filter so
- * we can use this function results to exclude those tables.
- *
* @param string $base_table
* The name of the Chado table used as a base table.
*
@@ -841,17 +845,11 @@ public function getAncillaryTablesWithCond(string $base_table) : array {
$tables = $this->getAncillaryTables($base_table);
foreach ($tables as $table_alias) {
$items = $this->getTableItems($base_table, $table_alias);
- $linker_cols = array_keys($items[0]['link_columns']);
foreach (array_keys($items[0]['conditions']) as $column_alias) {
- if (in_array($column_alias, $linker_cols) and
- $items[0]['link_columns'][$column_alias] == $base_table) {
- continue;
- }
- $ret_val[] = $table_alias;
-
+ $ret_val[$table_alias] = 1;
}
}
- return $ret_val;
+ return array_keys($ret_val);
}
/**
* Returns the list of tables currently handled by this object.
@@ -1064,7 +1062,8 @@ public function getColumnFieldAliases(string $base_table, string $table_alias, i
* @return bool
* TRUE if the value was set, FALSE otherwise
*/
- protected function setColumnValue(string $base_table, string $table_alias, int $delta, string $column_alias, $value) : bool {
+ protected function setColumnValue(string $base_table, string $table_alias,
+ int $delta, string $column_alias, $value) : bool {
if (!array_key_exists($base_table, $this->records)) {
throw new \Exception(t('ChadoRecords::setColumnValue(): The base table has not been added to the ChadoRecords object: @base_table.',
@@ -1086,6 +1085,7 @@ protected function setColumnValue(string $base_table, string $table_alias, int $
// Set the value.
$this->records[$base_table]['tables'][$table_alias]['items'][$delta]['values'][$column_alias] = $value;
+ $this->records[$base_table]['tables'][$table_alias]['items'][$delta]['has_values'] = TRUE;
return TRUE;
}
@@ -1105,7 +1105,7 @@ protected function setColumnValue(string $base_table, string $table_alias, int $
* @return mixed
* The value of the field.
*/
- public function getColumnValue(string $base_table, string $table_alias, int $delta, $column_alias) {
+ public function getColumnValue(string $base_table, string $table_alias, int $delta, string $column_alias) {
if (!array_key_exists($base_table, $this->records)) {
throw new \Exception(t('ChadoRecords::getFieldValue(): The base table has not been added to the ChadoRecords object: @base_table.',
@@ -1123,10 +1123,16 @@ public function getColumnValue(string $base_table, string $table_alias, int $del
return NULL;
}
- return $items[$delta]['values'][$column_alias];
+ // If the values were set then return it, otherwise return NULL;
+ if ($items[$delta]['has_values'] === TRUE) {
+ return $items[$delta]['values'][$column_alias];
+ }
+
+ return NULL;
}
+
/**
* Returns the records object as an array.
*
@@ -1523,10 +1529,11 @@ protected function validateRequired($base_table, $delta, $record_id, $record) {
if (count($missing) > 0) {
// Documentation for how to create a violation is here
// https://github.com/symfony/validator/blob/6.1/ConstraintViolation.php
- $message = 'The item cannot be saved because the following values are missing. ';
+ $message = 'The item cannot be saved because the following fields for the Chado '
+ . '"' . $base_table . '" table are missing. ';
$params = [];
foreach ($missing as $col) {
- $message .= ucfirst($col) . ", ";
+ $message .= $col . ", ";
}
$message = substr($message, 0, -2) . '.';
$this->violations[] = new ConstraintViolation(t($message, $params)->render(),
@@ -1635,37 +1642,36 @@ public function insertRecords(string $base_table, string $table_alias) {
}
/**
- * Queries for multiple records in Chado for a given table..
+ * Queries for multiple records in Chado for a given table.
*
* @param string $base_table
* The name of the Chado table used as a base table.
- * @param string $table_alias
- * The alias of the table. For the base table, use the same table name as
- * base tables don't have aliases.
+ * @param string $base_table_alias
+ * The alias of the base table.
+ *
+ * @return array
+ * An array of \Drupal\tripal_chado\TripalStorage\ChadoRecords objects.
*
* @throws \Exception
*/
- public function findRecords(string $base_table, string $table_alias) {
+ public function findRecords(string $base_table, string $base_table_alias) {
$found_records = [];
- // Make sure all IDs are up to date.
- $this->setLinks($base_table);
-
// Get information about this Chado table.
- $chado_table = $this->getTableFromAlias($base_table, $table_alias);
+ $chado_table = $this->getTableFromAlias($base_table, $base_table_alias);
- // Iterate through each item of the table and perform an insert.
- $items = $this->getTableItems($base_table, $table_alias);
+ // Iterate through each and perform a select.
+ $items = $this->getTableItems($base_table, $base_table_alias);
foreach ($items as $delta => $record) {
// Start the select
- $select = $this->connection->select('1:' . $chado_table, $table_alias);
+ $select = $this->connection->select('1:' . $chado_table, $base_table_alias);
// Add the fields in the chado table.
foreach ($record['columns'] as $column_alias) {
$chado_column = $record['column_aliases'][$column_alias]['chado_column'];
- $select->addField($table_alias, $chado_column, $column_alias);
+ $select->addField($base_table_alias, $chado_column, $column_alias);
}
// Add in any joins.
@@ -1696,9 +1702,8 @@ public function findRecords(string $base_table, string $table_alias) {
if (array_key_exists($column_alias, $record['link_columns']) and !$this->getRecordID($base_table)) {
continue;
}
- $select->condition($table_alias . '.' . $column_alias, $value['value'], $value['operation']);
+ $select->condition($base_table_alias . '.' . $column_alias, $value['value'], $value['operation']);
}
-
$this->field_debugger->reportQuery($select, "Select Query for $chado_table ($delta)");
// Execute the query.
@@ -1708,24 +1713,25 @@ public function findRecords(string $base_table, string $table_alias) {
['@table' => $chado_table, '@record' => print_r($record, TRUE)]));
}
+ // Iterate through the results and create a new record for each one.
while ($values = $results->fetchAssoc()) {
+ // We start by cloning the records array that was used to query.
+ $new_record = new ChadoRecords($this->field_debugger, $this->logger, $this->connection);
+ $new_record->copyRecords($this);
+
// Update the values in the new record.
foreach ($values as $column_alias => $value) {
- $this->setColumnValue($base_table, $table_alias, $delta, $column_alias, $value);
+ if ($value !== NULL) {
+ $new_record->setColumnValue($base_table, $base_table_alias, $delta, $column_alias, $value);
- // If this is the base table be sure to set the record ID.
- if ($base_table === $table_alias and array_key_exists($column_alias, $record['link_columns'])) {
- $this->setRecordID($base_table, $value);
- $this->setLinks($base_table);
+ // If this is the base table be sure to set the record ID.
+ if ($base_table === $base_table_alias and array_key_exists($column_alias, $record['link_columns'])) {
+ $new_record->setRecordID($base_table, $value);
+ }
}
}
- // We start by cloning the records array
- // (includes all tables, not just the current $base_table)
- $new_record = new ChadoRecords($this->field_debugger, $this->logger, $this->connection);
- $new_record->copyRecords($this);
-
// Save the new record object. to be returned later.
$found_records[] = $new_record;
}
@@ -1752,11 +1758,6 @@ public function updateRecords($base_table, $table_alias) {
// Get the Chado table for this given table alias.
$chado_table = $this->getTableFromAlias($base_table, $table_alias);
- // Get information about this Chado table.
- $schema = $this->connection->schema();
- $table_def = $schema->getTableDef($chado_table, ['format' => 'drupal']);
- $pkey = $table_def['primary key'];
-
// Iterate through each item of the table and perform an insert.
$items = $this->getTableItems($base_table, $table_alias);
foreach ($items as $delta => $record) {
@@ -1876,7 +1877,11 @@ public function deleteRecords(string $base_table, string $table_alias, bool $gra
}
/**
- * Selects a single record from Chado.
+ * Selects the items for a given table in a record object.
+ *
+ * This function is used for the findValues() and loadValues() functions so
+ * it needs to be able to find multiple records from the base table and
+ * multiple items from an ancillary table.
*
* @param string $base_table
* The name of the Chado table used as a base table.
@@ -1887,18 +1892,18 @@ public function deleteRecords(string $base_table, string $table_alias, bool $gra
* @throws \Exception
*
* @return int
- * Returns the number of records for this table that were found.
+ * Returns the number of items for this table that were found.
*/
- public function selectRecords(string $base_table, string $table_alias) : int {
+ public function selectItems(string $base_table, string $table_alias) : int {
- // Indicates the number of records that were found for this table.
+ // Indicates the number of items that were found for this table.
// We need to return the number found because even if no records are found
// the `values` array of $this->records will still have the values that were
// provided to it. Since we use that same array for updates/inserts it
// makes sense for those values to be there. So, we need something to
// indicate if we actually did find values on a `loadValues()` or
// `findValues()` call.
- $num_found = 0;
+ $items_found = 0;
// Make sure all IDs are up to date.
$this->setLinks($base_table);
@@ -1906,7 +1911,7 @@ public function selectRecords(string $base_table, string $table_alias) : int {
// Get the Chado table for this given table alias.
$chado_table = $this->getTableFromAlias($base_table, $table_alias);
- // Iterate through each item of the table and perform an insert.
+ // Iterate through each item of the table and perform a select.
$items = $this->getTableItems($base_table, $table_alias);
foreach ($items as $delta => $record) {
@@ -1942,7 +1947,8 @@ public function selectRecords(string $base_table, string $table_alias) : int {
$left_alias = $join_info['on']['left_alias'];
$left_column = $join_info['on']['left_column'];
- $select->leftJoin('1:' . $right_table, $right_alias, $left_alias . '.' . $left_column . '=' . $right_alias . '.' . $right_column);
+ $select->leftJoin('1:' . $right_table, $right_alias,
+ $left_alias . '.' . $left_column . '=' . $right_alias . '.' . $right_column);
foreach ($join_info['columns'] as $column) {
$join_column = $column['chado_column'];
@@ -1966,32 +1972,36 @@ public function selectRecords(string $base_table, string $table_alias) : int {
$results = $select->execute();
if (!$results) {
throw new \Exception(t('Failed to select record in the Chado "@table" table. Record: @record',
- ['@table' => $chado_table, '@record' => print_r($record, TRUE)]));
+ ['@table' => $chado_table, '@record' => print_r($record, TRUE)]));
}
// Update the values in the record.
- $num_records = $delta;
+ $current_items = count($this->getTableItems($base_table, $table_alias));
+ $i = 0;
while ($values = $results->fetchAssoc()) {
- // If we have more records than we have items then we have a field
- // with cardinality > 1 and this select is part of a findValues()
- // call. For a loadValues() then the delta should already be set for
- // all the property items. We need to create a copy of the previous
- // item so we can add the new values.
- if ($num_records + 1 > count($this->getTableItems($base_table, $table_alias))) {
+
+ // On a loadValues() then the record array will have all of the items
+ // available. On a findValues() the records array is empty and we need
+ // to expand it. The following will allow us to expand the
+ // items array if we don't have enough elements.
+ if ($delta + $i > $current_items - 1) {
$this->addEmptyTableItem($base_table, $table_alias);
}
+
foreach ($values as $column_alias => $value) {
- $this->setColumnValue($base_table, $table_alias, $num_records, $column_alias, $value);
- // If this is the base table be sure to set the record ID.
- if ($base_table === $table_alias and array_key_exists($column_alias, $record['link_columns'])) {
- $this->setRecordID($base_table, $value);
+ if ($value !== NULL) {
+ $this->setColumnValue($base_table, $table_alias, $delta + $i, $column_alias, $value);
+ // If this is the base table be sure to set the record ID.
+ if ($base_table === $table_alias and array_key_exists($column_alias, $record['link_columns'])) {
+ $this->setRecordID($base_table, $value);
+ }
}
}
- $num_records++;
- $num_found++;
+ $i++;
+ $items_found++;
}
}
- return $num_found;
+ return $items_found;
}
/**
diff --git a/tripal_chado/tests/src/Functional/Services/ChadoTripalPublishTest.php b/tripal_chado/tests/src/Functional/Services/ChadoTripalPublishTest.php
new file mode 100644
index 0000000000..9abfe47967
--- /dev/null
+++ b/tripal_chado/tests/src/Functional/Services/ChadoTripalPublishTest.php
@@ -0,0 +1,589 @@
+insert('1:organism');
+ $insert->fields([
+ 'genus' => $details['genus'],
+ 'species' => $details['species'],
+ 'type_id' => array_key_exists('type_id', $details) ? $details['type_id'] : NULL,
+ 'infraspecific_name' => array_key_exists('infraspecific_name', $details) ? $details['infraspecific_name'] : NULL,
+ 'abbreviation' => array_key_exists('abbreviation', $details) ? $details['abbreviation'] : NULL,
+ 'common_name' => array_key_exists('common_name', $details) ? $details['common_name'] : NULL,
+ 'comment' => array_key_exists('comment', $details) ? $details['comment'] : NULL,
+ ]);
+ return $insert->execute();
+ }
+
+ /**
+ * A helper function for adding a project record to Chado.
+ *
+ * @param \Drupal\tripal\TripalDBX\TripalDbxConnection $chado
+ * A chado database object.
+ * @param array $details
+ * The key/value pairs of entries for the project. The keys correspond
+ * to the columns of the project table.
+ * @return int
+ * The project_id
+ */
+ public function addChadoProject($chado, $details) {
+ $insert = $chado->insert('1:project');
+ $insert->fields([
+ 'name' => $details['name'],
+ 'description' => array_key_exists('description', $details) ? $details['description'] : NULL,
+ ]);
+ return $insert->execute();
+ }
+
+ /**
+ * A helper function for adding a contact record to Chado.
+ *
+ * @param \Drupal\tripal\TripalDBX\TripalDbxConnection $chado
+ * A chado database object.
+ * @param array $details
+ * The key/value pairs of entries for the contact. The keys correspond
+ * to the columns of the contact table.
+ * @return int
+ * The contact_id
+ */
+ public function addChadoContact($chado, $details) {
+ $insert = $chado->insert('1:contact');
+ $insert->fields([
+ 'name' => $details['name'],
+ 'type_id' => array_key_exists('type_id', $details) ? $details['type_id'] : NULL,
+ 'description' => array_key_exists('description', $details) ? $details['description'] : NULL,
+ ]);
+ return $insert->execute();
+ }
+
+ /**
+ * A helper function for adding a project_contact record to Chado.
+ *
+ * @param \Drupal\tripal\TripalDBX\TripalDbxConnection $chado
+ * A chado database object.
+ * @param array $details
+ * The key/value pairs of entries for the project_contact. The keys correspond
+ * to the columns of the project_contact table.
+ * @return int
+ * The project_contact_id
+ */
+ public function addChadoProjectContact($chado, $details) {
+ $insert = $chado->insert('1:project_contact');
+ $insert->fields([
+ 'project_id' => $details['project_id'],
+ 'contact_id' => $details['contact_id'],
+ ]);
+ return $insert->execute();
+ }
+
+ /**
+ * A helper function for adding a property to a record in Chado.
+ *
+ * @param \Drupal\tripal\TripalDBX\TripalDbxConnection $chado
+ * A chado database object.
+ * @param string $base_table
+ * The base table to which the property should be added.
+ * @param array $details
+ * The key/value pairs of entries for the property. The keys correspond
+ * to the columns of the property table.
+ *
+ * @return int
+ * The property primary key.
+ */
+ public function addProperty($chado, $base_table, $details) {
+
+ $insert = $chado->insert('1:' . $base_table . 'prop');
+ $insert->fields([
+ $base_table . '_id' => $details[$base_table . '_id'],
+ 'value' => $details['value'],
+ 'type_id' => $details['type_id'],
+ 'rank' => $details['rank'],
+ ]);
+ return $insert->execute();
+ }
+
+
+ /**
+ * A helper function to test if the elements of a field item are present.
+ *
+ * @param string $bundle
+ * The content type bundle name (e.g. 'organism').
+ * @param string $field_name
+ * The name of the field that should be queried.
+ * @param int $num_expected
+ * The number of items that are expected to be found when applying the
+ * conditions specified in the $match argument.
+ * @param array $match
+ * An array of key/value pairs where the keys are the column names of
+ * field table in Drupal and the values are those to match in a select
+ * condition. All fields other than the 'entity_id', 'bundle', 'delta'
+ * 'deleted', 'langcode', and 'revision' have the field name as a prefix.
+ * But the keys need not include the prefix, just the field property key.
+ * The field name prefix will be added automatically.
+ * @param array $check
+ * An array of key/value pairs where the keys are the column names of the
+ * field table in Drupal and the values are checked that they match
+ * what is in the table. The same rules apply for the key naming as in
+ * the $match argument.
+ */
+ public function checkFieldItem($bundle, $field_name, $num_expected, $match, $check) {
+
+ $drupal_columns = ['bundle', 'entity_id', 'revision' ,'delta', 'deleted', 'langcode'];
+
+ $public = \Drupal::service('database');
+ $select = $public->select('tripal_entity__' . $field_name, 'f');
+ $select->fields('f');
+ $select->condition('bundle', $bundle);
+ foreach ($match as $key => $val) {
+ $column_name = $key;
+ if (!in_array($key, $drupal_columns)) {
+ $column_name = $field_name . '_' . $key;
+ }
+ $select->condition($column_name, $val);
+ }
+ $select->orderBy('delta');
+ $result = $select->execute();
+ $records = $result->fetchAll();
+
+ $this->assertCount($num_expected, $records,
+ 'The number of items expected for field "' . $field_name .'" with bundle "'
+ . $bundle . '" is not correct.');
+
+ foreach ($records as $delta => $record) {
+
+ // Make sure we have an entity ID for the specified record.
+ $this->assertNotNull($record->entity_id,
+ 'The entity_id for a published item is missing for the field "'
+ . $field_name . '" at delta ' . $delta);
+
+ // Make sure the expected values are present.
+ foreach ($check as $key => $val) {
+ $column_name = $key;
+ if (!in_array($key, $drupal_columns)) {
+ $column_name = $field_name . '_' . $key;
+ }
+ $this->assertEquals($val, $record->$column_name,
+ 'The value for, "' . $column_name . '", is not correct what we expected.');
+ }
+ }
+ }
+
+ /**
+ * A helper function to add fields to the organism content types used in the tests.
+ */
+ public function attachOrganismPropertyFields() {
+
+ /** @var \Drupal\tripal\Services\TripalFieldCollection $fields_service **/
+ // Now add a ChadoProperty field for the two types of properties.
+ $fields_service = \Drupal::service('tripal.tripalfield_collection');
+ $prop_field1 = [
+ 'name' => 'field_note',
+ 'content_type' => 'organism',
+ 'label' => 'Note',
+ 'type' => 'chado_property_type_default',
+ 'description' => "A note about this organism.",
+ 'cardinality' => -1,
+ 'required' => FALSE,
+ 'storage_settings' => [
+ 'storage_plugin_id' => 'chado_storage',
+ 'storage_plugin_settings'=> [
+ 'base_table' => 'organism',
+ 'prop_table' => 'organismprop'
+ ],
+ ],
+ 'settings' => [
+ 'termIdSpace' => 'local',
+ 'termAccession' => "Note",
+ ],
+ 'display' => [
+ 'view' => [
+ 'default' => [
+ 'region' => 'content',
+ 'label' => 'above',
+ 'weight' => 15
+ ],
+ ],
+ 'form' => [
+ 'default'=> [
+ 'region'=> 'content',
+ 'weight' => 15
+ ],
+ ],
+ ],
+ ];
+ $reason = '';
+ $is_valid = $fields_service->validate($prop_field1, $reason);
+ $this->assertTrue($is_valid, $reason);
+ $is_added = $fields_service->addBundleField($prop_field1);
+ $this->assertTrue($is_added, 'The organism property field "local:Note" could not be added.');
+
+ // Now add a ChadoProperty field for the two types of properties.
+ $prop_field2 = [
+ 'name' => 'field_comment',
+ 'content_type' => 'organism',
+ 'label' => 'Comment',
+ 'type' => 'chado_property_type_default',
+ 'description' => "A comment about this organism.",
+ 'cardinality' => -1,
+ 'required' => FALSE,
+ 'storage_settings' => [
+ 'storage_plugin_id' => 'chado_storage',
+ 'storage_plugin_settings'=> [
+ 'base_table' => 'organism',
+ 'prop_table' => 'organismprop'
+ ],
+ ],
+ 'settings' => [
+ 'termIdSpace' => 'schema',
+ 'termAccession' => "comment",
+ ],
+ 'display' => [
+ 'view' => [
+ 'default' => [
+ 'region' => 'content',
+ 'label' => 'above',
+ 'weight' => 15
+ ],
+ ],
+ 'form' => [
+ 'default'=> [
+ 'region'=> 'content',
+ 'weight' => 15
+ ],
+ ],
+ ],
+ ];
+ $reason = '';
+ $is_valid = $fields_service->validate($prop_field2, $reason);
+ $this->assertTrue($is_valid, $reason);
+ $is_added = $fields_service->addBundleField($prop_field2);
+ $this->assertTrue($is_added,
+ 'The Organism property field "schema:comment" could not be added.');
+ }
+
+ /**
+ * Tests the TripalContentTypes class public functions.
+ */
+ public function testChadoTripalPublishService() {
+
+ // Prepare Chado
+ $chado = $this->createTestSchema(ChadoTestBrowserBase::PREPARE_TEST_CHADO);
+
+ // Add the CV terms. These normally get added during a prepare and
+ // the Chado schema is prepared but not Drupal schema and it needs to
+ // know about the terms used for content types and fields.
+ $terms_setup = \Drupal::service('tripal_chado.terms_init');
+ $terms_setup->installTerms();
+
+ // Create the terms for the field property storage types.
+ /** @var \Drupal\tripal\TripalVocabTerms\PluginManagers\TripalIdSpaceManager $idsmanager */
+ $idsmanager = \Drupal::service('tripal.collection_plugin_manager.idspace');
+
+ $local_db = $idsmanager->loadCollection('local', "chado_id_space");
+ $note_term = new TripalTerm();
+ $note_term->setName('Note');
+ $note_term->setIdSpace('local');
+ $note_term->setVocabulary('local');
+ $note_term->setAccession('Note');
+ $local_db->saveTerm($note_term);
+
+ $schema_db = $idsmanager->loadCollection('schema', "chado_id_space");
+ $comment_term = new TripalTerm();
+ $comment_term->setName('comment');
+ $comment_term->setIdSpace('schema');
+ $comment_term->setVocabulary('schema');
+ $comment_term->setAccession('comment');
+ $schema_db->saveTerm($comment_term);
+
+ // Make sure we have the content types and fields that we want to test.
+ $collection_ids = ['general_chado'];
+ $content_type_setup = \Drupal::service('tripal.tripalentitytype_collection');
+ $fields_setup = \Drupal::service('tripal.tripalfield_collection');
+ $content_type_setup->install($collection_ids);
+ $fields_setup->install($collection_ids);
+
+ /** @var \Drupal\tripal\Services\TripalPublish $publish */
+ $publish = \Drupal::service('tripal.publish');
+
+ //
+ // Test publishing when no records are available.
+ //
+ $publish->init('organism', 'chado_storage');
+ $entities = $publish->publish();
+ $this->assertTrue(count($entities) == 0,
+ 'The TripalPublish service should return 0 entities when no records are available.');
+
+ //
+ // Test publishing a single record.
+ //
+ $taxrank_db = $idsmanager->loadCollection('TAXRANK', "chado_id_space");
+ $subspecies_term_id = $taxrank_db->getTerm('0000023')->getInternalId();
+
+ $organism_id = $this->addChadoOrganism($chado, [
+ 'genus' => 'Oryza',
+ 'species' => 'species',
+ 'abbreviation' => 'O. sativa',
+ 'type_id' => $subspecies_term_id,
+ 'infraspecific_name' => 'Japonica',
+ 'comment' => 'rice is nice'
+ ]);
+ $entities = $publish->publish();
+ $this->assertTrue(count(array_keys($entities)) == 1,
+ 'The TripalPublish service should have published 1 organism.');
+
+ // Test that entries were added for all field items and that fields that
+ // shouldn't be saved in Drupal are NULL.
+ $this->checkFieldItem('organism', 'organism_genus', 1,
+ ['record_id' => $organism_id],
+ ['bundle' => 'organism', 'entity_id' => 1, 'value' => NULL]);
+
+ $this->checkFieldItem('organism', 'organism_species', 1,
+ ['record_id' => $organism_id],
+ ['bundle' => 'organism', 'entity_id' => 1, 'value' => NULL]);
+
+ $this->checkFieldItem('organism', 'organism_abbreviation', 1,
+ ['record_id' => $organism_id],
+ ['bundle' => 'organism', 'entity_id' => 1, 'value' => NULL]);
+
+ $this->checkFieldItem('organism', 'organism_infraspecific_name', 1,
+ ['record_id' => $organism_id],
+ ['bundle' => 'organism', 'entity_id' => 1, 'value' => NULL]);
+
+ $this->checkFieldItem('organism', 'organism_infraspecific_type', 1,
+ ['record_id' => $organism_id],
+ ['bundle' => 'organism', 'entity_id' => 1, 'type_id' => NULL]);
+
+ $this->checkFieldItem('organism', 'organism_comment', 1,
+ ['record_id' => $organism_id],
+ ['bundle' => 'organism', 'entity_id' => 1, 'value' => NULL]);
+
+ // Test that the title via token replacement is working.
+ $this->assertTrue(array_values($entities)[0] == 'Oryza species subspecies Japonica',
+ 'The title of a Chado organism is incorrect after publishing: ' . array_values($entities)[0] . '!=' . 'Oryza species subspecies Japonica');
+
+ //
+ // Test a second entity. Also use a title without all tokens
+ //
+ $organism_id2 = $this->addChadoOrganism($chado, [
+ 'genus' => 'Gorilla',
+ 'species' => 'gorilla',
+ 'abbreviation' => 'G. gorilla',
+ 'comment' => 'Gorilla'
+ ]);
+ $entities = $publish->publish();
+ $this->assertEquals('Gorilla gorilla ', array_values($entities)[0],
+ 'The title of Chado organism with missing tokens is incorrect after publishing');
+
+
+ $this->checkFieldItem('organism', 'organism_genus', 1,
+ ['record_id' => $organism_id2],
+ ['bundle' => 'organism', 'entity_id' => 2, 'value' => NULL]);
+
+ $this->checkFieldItem('organism', 'organism_species', 1,
+ ['record_id' => $organism_id2],
+ ['bundle' => 'organism', 'entity_id' => 2, 'value' => NULL]);
+
+ $this->checkFieldItem('organism', 'organism_abbreviation', 1,
+ ['record_id' => $organism_id2],
+ ['bundle' => 'organism', 'entity_id' => 2, 'value' => NULL]);
+
+ $this->checkFieldItem('organism', 'organism_infraspecific_name', 0,
+ ['record_id' => $organism_id2],
+ ['bundle' => 'organism', 'entity_id' => 2, 'value' => NULL]);
+
+ $this->checkFieldItem('organism', 'organism_infraspecific_type', 1,
+ ['record_id' => $organism_id2],
+ ['bundle' => 'organism', 'entity_id' => 2, 'type_id' => NULL]);
+
+ $this->checkFieldItem('organism', 'organism_comment', 1,
+ ['record_id' => $organism_id2],
+ ['bundle' => 'organism', 'entity_id' => 2, 'value' => NULL]);
+
+ //
+ // Test publishing properties.
+ //
+ $comment_type_id = $schema_db->getTerm('comment')->getInternalId();
+ $note_type_id = $local_db->getTerm('Note')->getInternalId();
+ $this->attachOrganismPropertyFields();
+ $this->addProperty($chado, 'organism', [
+ 'organism_id' => $organism_id,
+ 'type_id' => $note_type_id,
+ 'value' => 'Note 1',
+ 'rank' => 1,
+ ]);
+ $this->addProperty($chado, 'organism', [
+ 'organism_id' => $organism_id,
+ 'type_id' => $note_type_id,
+ 'value' => 'Note 0',
+ 'rank' => 0,
+ ]);
+ $this->addProperty($chado, 'organism', [
+ 'organism_id' => $organism_id,
+ 'type_id' => $note_type_id,
+ 'value' => 'Note 2',
+ 'rank' => 2,
+ ]);
+
+ $this->addProperty($chado, 'organism', [
+ 'organism_id' => $organism_id,
+ 'type_id' => $comment_type_id,
+ 'value' => 'Comment 0',
+ 'rank' => 0,
+ ]);
+ $this->addProperty($chado, 'organism', [
+ 'organism_id' => $organism_id,
+ 'type_id' => $comment_type_id,
+ 'value' => 'Comment 1',
+ 'rank' => 1,
+ ]);
+
+
+ // Now publish the organism content type again.
+ $publish->init('organism', 'chado_storage');
+ $entities = $publish->publish();
+
+ // Because we added properties for the first organism we should set it's
+ // entity in those returned, but not the gorilla organism.
+ $this->assertEquals('Oryza species subspecies Japonica', array_values($entities)[0],
+ 'The Oryza species subspecies Japonica organism should appear in the published list because it has new properties.');
+ $this->assertCount(1, array_values($entities),
+ 'There should only be one published entity for a single organism with new properties.');
+
+ // Check that the property values got published. The type_id should be
+ // NULL because that's not stored in Drupal.
+ $this->checkFieldItem('organism', 'field_note', 1,
+ ['record_id' => $organism_id, 'prop_id' => 1],
+ ['type_id' => NULL, 'linker_id' => $organism_id,
+ 'bundle' => 'organism', 'entity_id' => 1]);
+
+ $this->checkFieldItem('organism', 'field_note', 1,
+ ['record_id' => $organism_id, 'prop_id' => 2],
+ ['type_id' => NULL, 'linker_id' => $organism_id,
+ 'bundle' => 'organism', 'entity_id' => 1]);
+
+ $this->checkFieldItem('organism', 'field_note', 1,
+ ['record_id' => $organism_id, 'prop_id' => 3],
+ ['type_id' => NULL, 'linker_id' => $organism_id,
+ 'bundle' => 'organism', 'entity_id' => 1]);
+
+ $this->checkFieldItem('organism', 'field_comment', 1,
+ ['record_id' => $organism_id, 'prop_id' => 4],
+ ['type_id' => NULL, 'linker_id' => $organism_id,
+ 'bundle' => 'organism', 'entity_id' => 1]);
+
+ $this->checkFieldItem('organism', 'field_comment', 1,
+ ['record_id' => $organism_id, 'prop_id' => 5],
+ ['type_id' => NULL, 'linker_id' => $organism_id,
+ 'bundle' => 'organism', 'entity_id' => 1]);
+
+ // Check that only the exact number of properties were published.
+ $this->checkFieldItem('organism', 'field_note', 3, ['entity_id' => 1], []);
+ $this->checkFieldItem('organism', 'field_comment', 2, ['entity_id' => 1], []);
+
+ //
+ // Test publishing a field that uses a linker table.
+ //
+
+ // Create and publish the contacts and the project.
+ $contact_db = $idsmanager->loadCollection('TCONTACT', "chado_id_space");
+ $person_term_id = $contact_db->getTerm('0000003')->getInternalId();
+ $contact_id1 = $this->addChadoContact($chado, [
+ 'name' => 'John Doe',
+ 'type_id' => $person_term_id,
+ 'description' => 'Bioinformaticist extrodinaire'
+ ]);
+ $contact_id2 = $this->addChadoContact($chado, [
+ 'name' => 'Lady Gaga',
+ 'type_id' => $person_term_id,
+ 'description' => 'Pop star'
+ ]);
+ $project_id1 = $this->addChadoProject($chado, [
+ 'name' => 'Bad Project',
+ 'description' => 'Want your bad project'
+ ]);
+ $project_id2 = $this->addChadoProject($chado, [
+ 'name' => 'Project Face',
+ 'description' => 'I wanna project like they do in Texas, please'
+ ]);
+ $project_contact_id1 = $this->addChadoProjectContact($chado, [
+ 'project_id' => $project_id1,
+ 'contact_id' => $contact_id1,
+ ]);
+ $project_contact_id2 = $this->addChadoProjectContact($chado, [
+ 'project_id' => $project_id1,
+ 'contact_id' => $contact_id2,
+ ]);
+ $project_contact_id3 = $this->addChadoProjectContact($chado, [
+ 'project_id' => $project_id2,
+ 'contact_id' => $contact_id2,
+ ]);
+
+ // Now publish the projects and contacts. We check that 3 items are
+ // published because there is a null contact and currently there is
+ // nothing to prevent that contact from being published. (Issue #1809)
+ $publish->init('contact', 'chado_storage');
+ $entities = $publish->publish();
+ $this->assertCount(3, $entities,
+ 'Failed to publish 3 contact entities.');
+
+ $publish->init('project', 'chado_storage');
+ $entities = $publish->publish();
+ $this->assertCount(2, $entities,
+ 'Failed to publish 2 project entities.');
+
+ // Make sure that the linked records are also published for each project.
+ // The chado_contact_type_default is the field we're testing got published.
+ $this->checkFieldItem('project', 'project_contact', 1,
+ ['record_id' => $project_id1, 'linker_id' => $project_contact_id1],
+ ['link' => $project_id1, 'bundle' => 'project', 'entity_id' => 6, 'contact_id' => $contact_id1]);
+
+ $this->checkFieldItem('project', 'project_contact', 1,
+ ['record_id' => $project_id1, 'linker_id' => $project_contact_id2],
+ ['link' => $project_id1, 'bundle' => 'project', 'entity_id' => 6, 'contact_id' => $contact_id2]);
+
+ $this->checkFieldItem('project', 'project_contact', 1,
+ ['record_id' => $project_id2, 'linker_id' => $project_contact_id3],
+ ['link' => $project_id2, 'bundle' => 'project', 'entity_id' => 7, 'contact_id' => $contact_id2]);
+
+ // Check that only the exact number of linked items were published.
+ $this->checkFieldItem('project', 'project_contact', 2, ['entity_id' => 6], []);
+ $this->checkFieldItem('project', 'project_contact', 1, ['entity_id' => 7], []);
+
+ }
+}