'Medtr4g030710' ); $features = chado_generate_var('feature', $values); * @endcode * This will return an object if there is only one feature with the name Medtr4g030710 or it will * return an array of feature objects if more than one feature has that name. * * Some tables and fields are excluded by default. To have those tables & fields added to * your variable you can use the * chado_expand_var([chado variable], [type], [what to expand], [optional options]) * function. An example of how to use this function is: * @code // Get a chado object to be expanded $values = array( 'name' => 'Medtr4g030710' ); $features = chado_generate_var('feature', $values); // Expand the organism node $feature = chado_expand_var($feature, 'node', 'organism'); // Expand the feature.residues field $feature = chado_expand_var($feature, 'field', 'feature.residues'); // Expand the feature properties (featureprop table) $feature = chado_expand_var($feature, 'table', 'featureprop'); * @endcode */ /** * Implements hook_exclude_type_by_default() * * This hooks allows fields of a specified type that match a specified criteria to be excluded by * default from any table when chado_generate_var() is called. Keep in mind that if * fields are excluded by default they can always be expanded at a later date using * chado_expand_var(). * * Criteria are php strings that evaluate to either TRUE or FALSE. These strings are evaluated using * drupal_eval() which suppresses syntax errors and throws watchdog entries of type php. There are * also watchdog entries of type tripal_core stating the exact criteria evaluated. Criteria can * contain the following tokens: * - >field_name< * Replaced by the name of the field to be excluded * - >field_value< * Replaced by the value of the field in the current record * Also keep in mind that if your criteria doesn't contain the >field_value< token then it will be * evaluated before the query is executed and if the field is excluded it won't be included in the * query. * * @return * An array of type => criteria where the type is excluded if the criteria evaluates to TRUE * * @ingroup tripal_chado_variables_api */ function tripal_core_exclude_type_by_default() { return array('text' => 'strlen(">field_value< ") > 100'); } /** * Implements hook_exclude_field_from__by_default() * * This hooks allows fields from a specified table that match a specified criteria to be excluded by * default from any table when chado_generate_var() is called. Keep in mind that if * fields are excluded by default they can always be expanded at a later date using * chado_expand_var(). * * Criteria are php strings that evaluate to either TRUE or FALSE. These strings are evaluated using * drupal_eval() which suppresses syntax errors and throws watchdog entries of type php. There are * also watchdog entries of type tripal_core stating the exact criteria evaluated. Criteria can * contain the following tokens: * - >field_name< * Replaced by the name of the field to be excluded * - >field_value< * Replaced by the value of the field in the current record * Also keep in mind that if your criteria doesn't contain the >field_value< token then it will be * evaluated before the query is executed and if the field is excluded it won't be included in the * query. * * @return * An array of type => criteria where the type is excluded if the criteria evaluates to TRUE * * @ingroup tripal_chado_variables_api */ function tripal_core_exclude_field_from_feature_by_default() { return array(); } /** * Generates an object containing the full details of a record(s) in chado. * * This differs from the objects returned by chado_select_record in so far as all foreign key * relationships have been followed meaning you have more complete details. Thus this function * should be used whenever you need a full variable and chado_select_record should be used if * you only case about a few columns. * * @param $table * The name of the base table to generate a variable for * @param $values * A select values array that selects the records you want from the base table * (this has the same form as chado_select_record) * @param $base_options * An array containing options for the base table. For example, an * option of 'order_by' may be used to sort results in the base table * if more than one are returned. The options must be compatible with * the options accepted by the chado_select_record() function. * Additionally, These options are available for this function: * -return_array: * can be provided to force the function to always return an array. Default * behavior is to return a single record if only one record exists or to return * an array if multiple records exist. * - include_fk: * an array of FK relationships to follow. By default, the * chado_select_record function will follow all FK relationships but this * may generate more queries then is desired slowing down this function call when * there are lots of FK relationships to follow. Provide an array specifying the * fields to include. For example, if expanding a property table (e.g. featureprop) * and you want the CV and accession but do not want the DB the following * array would work: * * $table_options = array( * 'include_fk' => array( * 'type_id' => array( * 'cv_id' => 1, * 'dbxref_id' => 1, * ) * ) * ); * * The above array will expand the 'type_id' of the property table but only * further expand the cv_id and the dbxref_id and will go no further. * - pager: * Use this option if it is desired to return only a subset of results * so that they may be shown within a Drupal-style pager. This should be * an array with two keys: 'limit' and 'element'. The value of 'limit' * should specify the number of records to return and 'element' is a * unique integer to differentiate between pagers when more than one * appear on a page. The 'element' should start with zero and increment by * one for each pager. This only works when type is a 'table'. * @return * Either an object (if only one record was selected from the base table) * or an array of objects (if more than one record was selected from the base table). * If the option 'return_array' is provided the function always returns an array. * * Example Usage: * @code $values = array( 'name' => 'Medtr4g030710' ); $features = chado_generate_var('feature', $values); * @endcode * This will return an object if there is only one feature with the name Medtr4g030710 or it will * return an array of feature objects if more than one feature has that name. * * Note to Module Designers: Fields can be excluded by default from these objects by implementing * one of the following hooks: * - hook_exclude_field_from_tablename_by_default (where tablename is the name of the table): * This hook allows you to add fields to be excluded on a per table basis. Simply implement * this hook to return an array of fields to be excluded. For example: * @code mymodule_exclude_field_from_feature_by_default() { return array('residues' => TRUE); } * @endcode * will ensure that feature.residues is ecluded from a feature object by default. * - hook_exclude_type_by_default: * This hook allows you to exclude fields from all tables that are of a given postgresql field * type. Simply implement this hook to return an array of postgresql types mapped to criteria. * Then all fields of that type where the criteria supplied returns TRUE will be excluded from * any table. Tokens available in criteria are >field_value< and >field_name< . For example: * @code mymodule_exclude_type_by_default() { return array('text' => 'length(>field_value< ) > 50'); } * @endcode * will exclude all text fields with a length > 50. Thus if $feature.residues is longer than 50 * it will be excluded, otherwise it will be added. * * @ingroup tripal_chado_variables_api */ function chado_generate_var($table, $values, $base_options = array()) { $all = new stdClass(); $return_array = 0; if (array_key_exists('return_array', $base_options)) { $return_array = 1; } $include_fk = 0; if (array_key_exists('include_fk', $base_options)) { $include_fk = $base_options['include_fk']; } $pager = array(); if (array_key_exists('pager', $base_options)) { $pager = $base_options['pager']; } // get description for the current table---------------------------------------------------------- $table_desc = chado_get_schema($table); if (!$table_desc or count($table_desc) == 0) { tripal_report_error('tripal_core', TRIPAL_ERROR, "chado_generate_var: The table '%table' has not been defined. " . "and cannot be expanded. If this is a custom table, please add it using the Tripal " . "custom table interface.", array('%table' => $table)); if ($return_array) { return array(); } return FALSE; } $table_primary_key = $table_desc['primary key'][0]; $table_columns = array_keys($table_desc['fields']); // Expandable fields without value needed for criteria-------------------------------------------- $all->expandable_fields = array(); if (array_key_exists('referring_tables', $table_desc) and $table_desc['referring_tables']) { $all->expandable_tables = $table_desc['referring_tables']; } else { $all->expandable_tables = array(); } $all->expandable_nodes = array(); /* // Get fields to be removed by name................................. $fields_to_remove = module_invoke_all('exclude_field_from_' . $table . '_by_default'); foreach ($fields_to_remove as $field_name => $criteria) { //replace >field_name< with the current field name & $criteria = preg_replace('/>field_name< /', addslashes($field_name), $criteria); // if field_value needed we can't deal with this field yet if (preg_match('/>field_value< /', $criteria)) { break; } //if criteria then remove from query // @coder-ignore: only module designers can populate $criteria -not security risk $success = php_eval(''); if ($success) { unset($table_columns[array_search($field_name, $table_columns)]); unset($fields_to_remove[$field_name]); $all->expandable_fields[] = $table . '.' . $field_name; } } //Get fields to be removed by type................................ $types_to_remove = module_invoke_all('exclude_type_by_default'); $field_types = array(); foreach ($table_desc['fields'] as $field_name => $field_array) { $field_types[$field_array['type']][] = $field_name; } foreach ($types_to_remove as $field_type => $criteria) { // if there are fields of that type to remove if (is_array($field_types[$field_type])) { //replace >field_name< with the current field name & $criteria = preg_replace('/>field_name< /', addslashes($field_name), $criteria); foreach ($field_types[$field_type] as $field_name) { // if field_value needed we can't deal with this field yet if (preg_match('/>field_value< /', $criteria)) { $fields_to_remove[$field_name] = $criteria; continue; } // if field_value needed we can't deal with this field yet if (preg_match('/>field_value< /', $criteria)) { break; } //if criteria then remove from query // @coder-ignore: only module designers can populate $criteria -not security risk $success = php_eval(''); if ($success) { unset($table_columns[array_search($field_name, $table_columns)]); $all->expandable_fields[] = $table . '.' . $field_name; } } //end of foreach field of that type } } //end of foreach type to be removed */ // get the values for the record in the current table--------------------------------------------- $results = chado_select_record($table, $table_columns, $values, $base_options); if ($results) { foreach ($results as $key => $object) { // Add empty expandable_x arrays $object->expandable_fields = $all->expandable_fields; $object->expandable_tables = $all->expandable_tables; $object->expandable_nodes = $all->expandable_nodes; // add curent table $object->tablename = $table; // check if the current table maps to a node type----------------------------------------------- // if this table is connected to a node there will be a chado_tablename table in drupal if (db_table_exists('chado_' . $table)) { // that has a foreign key to this one ($table_desc['primary key'][0] // and to the node table (nid) $sql = " SELECT $table_primary_key, nid FROM {chado_$table} WHERE $table_primary_key = :$table_primary_key "; $mapping = db_query($sql, array(":$table_primary_key" => $object->{$table_primary_key}))->fetchObject(); if ($mapping and $mapping->$table_primary_key) { $object->nid = $mapping->nid; $object->expandable_nodes[] = $table; } } // remove any fields where criteria needs to be evalulated--------------------------------------- /* foreach ($fields_to_remove as $field_name => $criteria) { if (!isset($object->{$field_name})) { break; } $criteria = preg_replace('/>field_value< /', addslashes($object->{$field_name}), $criteria); $success = php_eval(''); if ($success) { unset($object->{$field_name}); $object->expandable_fields[] = $table . '.' . $field_name; } } */ // recursively follow foreign key relationships nesting objects as we go------------------------ if ($table_desc['foreign keys']) { foreach ($table_desc['foreign keys'] as $foreign_key_array) { $foreign_table = $foreign_key_array['table']; foreach ($foreign_key_array['columns'] as $foreign_key => $primary_key) { // Note: Foreign key is the field in the current table whereas primary_key is the field in // the table referenced by the foreign key //Dont do anything if the foreign key is empty if (empty($object->{$foreign_key})) { continue; } if ($include_fk) { // don't recurse if the callee has supplied an $fk_include list and this // FK table is not in the list. if (is_array($include_fk) and !array_key_exists($foreign_key, $include_fk)) { continue; } // if we have the option but it is not an array then we don't recurse any furutehr if (!is_array($include_fk)) { continue; } } // get the record from the foreign table $foreign_values = array($primary_key => $object->{$foreign_key}); $options = array(); if (is_array($include_fk)) { $options['include_fk'] = $include_fk[$foreign_key]; } $foreign_object = chado_generate_var($foreign_table, $foreign_values, $options); // add the foreign record to the current object in a nested manner $object->{$foreign_key} = $foreign_object; // Flatten expandable_x arrays so only in the bottom object if (property_exists($object->{$foreign_key}, 'expandable_fields') and is_array($object->{$foreign_key}->expandable_fields)) { $object->expandable_fields = array_merge( $object->expandable_fields, $object->{$foreign_key}->expandable_fields ); unset($object->{$foreign_key}->expandable_fields); } if (property_exists($object->{$foreign_key}, 'expandable_tables') and is_array($object->{$foreign_key}->expandable_tables)) { $object->expandable_tables = array_merge( $object->expandable_tables, $object->{$foreign_key}->expandable_tables ); unset($object->{$foreign_key}->expandable_tables); } if (property_exists($object->{$foreign_key}, 'expandable_nodes') and is_array($object->{$foreign_key}->expandable_nodes)) { $object->expandable_nodes = array_merge( $object->expandable_nodes, $object->{$foreign_key}->expandable_nodes ); unset($object->{$foreign_key}->expandable_nodes); } } } $results[$key] = $object; } } } // convert the results into an array $results_arr = array(); foreach ($results as $record) { $results_arr[] = $record; } // check only one result returned if (!$return_array) { if (sizeof($results_arr) == 1) { // add results to object return $results_arr[0]; } elseif (!empty($results_arr)) { return $results_arr; } else { // no results returned } } // the caller has requested results are always returned as // an array else { if (!$results_arr) { return array(); } else { return $results_arr; } } } /** * Retrieves fields/tables/nodes that were excluded by default from a variable and adds them * * This function exists to allow chado_generate_var() to excldue some * fields/tables/nodes from the default form of a variable without making it extremely difficult for * the tripal admin to get at these variables if he/she wants them. * * @param $object * This must be an object generated using chado_generate_var() * @param $type * Must be one of 'field', 'table', 'node'. Indicates what is being expanded. * @param $to_expand * The name of the field/table/node to be expanded * @param $table_options * - order_by: * An array containing options for the base table. For example, an * option of 'order_by' may be used to sort results in the base table * if more than one are returned. The options must be compatible with * the options accepted by the chado_select_record() function. * - return_array: * Additionally, The option 'return_array' can be provided to force * the function to expand tables as an array. Default behavior is to expand * a table as single record if only one record exists or to expand as an array if * multiple records exist. * - include_fk: * an array of FK relationships to follow. By default, the * chado_expand_var function will follow all FK relationships but this * may generate more queries then is desired slowing down this function call when * there are lots of FK relationships to follow. Provide an array specifying the * fields to include. For example, if expanding a property table (e.g. featureprop) * and you want the CV and accession but do not want the DB the following * array would work: * $table_options = array( * 'include_fk' => array( * 'type_id' => array( * 'cv_id' => 1, * 'dbxref_id' => 1, * ) * ) * ); * * The above array will expand the 'type_id' of the property table but only * further expand the cv_id and the dbxref_id and will go no further. * - pager: * Use this option if it is desired to return only a subset of results * so that they may be shown within a Drupal-style pager. This should be * an array with two keys: 'limit' and 'element'. The value of 'limit' * should specify the number of records to return and 'element' is a * unique integer to differentiate between pagers when more than one * appear on a page. The 'element' should start with zero and increment by * one for each pager. This only works when type is a 'table'. * @return * A chado object supplemented with the field/table/node requested to be expanded. * If the type is a table and it has already been expanded no changes is made to the * returned object * * Example Usage: * @code // Get a chado object to be expanded $values = array( 'name' => 'Medtr4g030710' ); $features = chado_generate_var('feature', $values); // Expand the organism node $feature = chado_expand_var($feature, 'node', 'organism'); // Expand the feature.residues field $feature = chado_expand_var($feature, 'field', 'feature.residues'); // Expand the feature properties (featureprop table) $feature = chado_expand_var($feature, 'table', 'featureprop'); * @endcode * * @ingroup tripal_chado_variables_api */ function chado_expand_var($object, $type, $to_expand, $table_options = array()) { // make sure we have a value if (!$object) { tripal_report_error('tripal_core', TRIPAL_ERROR, 'Cannot pass non array as argument, $object, to chado_expand_var function.', array()); return $object; } // check to see if we are expanding an array of objects if (is_array($object)) { foreach ($object as $index => $o) { $object[$index] = chado_expand_var($o, $type, $to_expand); } return $object; } // get the base table name $base_table = $object->tablename; switch ($type) { case "field": //-------------------------------------------------------------------------------- if (preg_match('/(\w+)\.(\w+)/', $to_expand, $matches)) { $tablename = $matches[1]; $fieldname = $matches[2]; $table_desc = chado_get_schema($tablename); $values = array(); foreach ($table_desc['primary key'] as $key) { if(property_exists($object, $key)) { $values[$key] = $object->{$key}; } } if ($base_table == $tablename) { //get the field $results = chado_select_record($tablename, array($fieldname), $values); $object->{$fieldname} = $results[0]->{$fieldname}; $object->expanded = $to_expand; } else { //We need to recurse -the field is in a nested object foreach ((array) $object as $field_name => $field_value) { if (is_object($field_value)) { $object->{$field_name} = chado_expand_var( $field_value, 'field', $to_expand ); } } //end of for each field in the current object } } else { tripal_report_error('tripal_core', TRIPAL_ERROR, 'chado_expand_var: Field (%field) not in the right format. " . "It should be .'); } break; case "table": //-------------------------------------------------------------------------------- $foreign_table = $to_expand; // don't expand the table it already is expanded if (array_key_exists($foreign_table, $object)) { return $object; } $foreign_table_desc = chado_get_schema($foreign_table); // If it's connected to the base table via a FK constraint if (array_key_exists($base_table, $foreign_table_desc['foreign keys'])) { foreach ($foreign_table_desc['foreign keys'][$base_table]['columns'] as $left => $right) { // if the FK value in the base table is not there then we can't expand it, so just skip it. if (!$object->{$right}) { continue; } // generate a new object for this table using the FK values in the base table. $new_options = $table_options; $foreign_object = chado_generate_var($foreign_table, array($left => $object->{$right}), $new_options); // if the generation of the object was successful, update the base object to include it. if ($foreign_object) { // in the case where the foreign key relationship exists more // than once with the same table we want to alter the array structure to // include the field name. if (count($foreign_table_desc['foreign keys'][$base_table]['columns']) > 1) { if (!property_exists($object, $foreign_table)) { $object->{$foreign_table} = new stdClass(); } $object->{$foreign_table}->{$left} = $foreign_object; $object->expanded = $to_expand; } else { if (!property_exists($object, $foreign_table)) { $object->{$foreign_table} = new stdClass(); } $object->{$foreign_table} = $foreign_object; $object->expanded = $to_expand; } } // if the object returned is NULL then handle that else { // in the case where the foreign key relationship exists more // than once with the same table we want to alter the array structure to // include the field name. if (count($foreign_table_desc['foreign keys'][$base_table]['columns']) > 1) { if (!property_exists($object, $foreign_table)) { $object->{$foreign_table} = new stdClass(); } $object->{$foreign_table}->{$left} = NULL; } else { $object->{$foreign_table} = NULL; } } } } // if the foreign table is not connected to the base table through a FK constraint else { // We need to recurse -the table has a relationship to one of the nested objects $did_expansion = 0; foreach ((array) $object as $field_name => $field_value) { // if we have a nested object ->expand the table in it // check to see if the $field_name is a valid chado table, we don't need // to call chado_expand_var on fields that aren't tables $check = chado_get_schema($field_name); if ($check) { $did_expansion = 1; $object->{$field_name} = chado_expand_var($field_value, 'table', $foreign_table); } } // if we did not expand this table we should return a message that the foreign table // could not be expanded if (!$did_expansion) { tripal_report_error('tripal_core', TRIPAL_ERROR, 'chado_expand_var: Could not expand table, %table. It is ' . 'not in a foreign key relationship with the base object nor with any other expanded table. ' . 'Check the table definition to ensure that a proper foreign key relationship is present.', array('%table' => $foreign_table)); } } break; case "node": //--------------------------------------------------------------------------------- //if the node to be expanded is for our base table, then just expand it if ($object->tablename == $to_expand) { $node = node_load($object->nid); if ($node) { $object->expanded = $to_expand; $node->expandable_fields = $object->expandable_fields; unset($object->expandable_fields); $node->expandable_tables = $object->expandable_tables; unset($object->expandable_tables); $node->expandable_nodes = $object->expandable_nodes; unset($object->expandable_nodes); $node->{$base_table} = $object; $object = $node; } else { tripal_report_error('tripal_core', TRIPAL_ERROR, 'chado_expand_var: No node matches the nid (%nid) supplied.', array('%nid' => $object->nid)); } //end of if node } else { //We need to recurse -the node to expand is one of the nested objects foreach ((array) $object as $field_name => $field_value) { if (is_object($field_value)) { $object->{$field_name} = chado_expand_var( $field_value, 'node', $to_expand ); } } //end of for each field in the current object } break; default: tripal_report_error('tripal_core', TRIPAL_ERROR, 'chado_expand_var: Unrecognized type (%type). Should be one of "field", "table", "node".', array('%type' => $type)); return FALSE; } // move extended array downwards if (!property_exists($object, 'expanded')) { //if there's no extended field then go hunting for it foreach ( (array)$object as $field_name => $field_value) { if (is_object($field_value)) { if (isset($field_value->expanded)) { $object->expanded = $field_value->expanded; unset($field_value->expanded); } } } } //try again becasue now we might have moved it down if (property_exists($object, 'expanded')) { $expandable_name = 'expandable_' . $type . 's'; if (property_exists($object, $expandable_name) and $object->{$expandable_name}) { $key_to_remove = array_search($object->expanded, $object->{$expandable_name}); unset($object->{$expandable_name}[$key_to_remove]); unset($object->expanded); } else { // if there is an expandable array then we've reached the base object // if we get here and don't have anything expanded then something went wrong // tripal_report_error('tripal_core', TRIPAL_ERROR, // 'chado_expand_var: Unable to expand the %type %to_expand', // array('%type'=>$type, '%to_expand'=>$to_expand), // ); } //end of it we've reached the base object } return $object; }