'2.0', 'path' => drupal_get_path('module', 'data'), ); } /** * Implementation of hook_features_api(). */ function data_features_api() { return array( 'default_hook' => 'data_default', 'file' => drupal_get_path('module', 'data') .'/data.features.inc', ); } /** * Create a table. * * Usage: * $table = data_create_table('my_table', $schema, 'My table'); * $table->save($data); * * @see DataTable class. * * @param $name * String that identifies the data table. It is recommended to use * data_name() to generate a table name in the data namespace. For * example: $table = data_get_tabe(data_name('my_table')); * @param $schema * Schema for the table. * @param $title * A natural title for the table. * * @return * A DataTable object if init could create one, * FALSE if not. */ function data_create_table($name, $schema, $title = NULL) { if (_data_get_table($name, NULL, NULL, TRUE)) { return FALSE; } return _data_get_table($name, $schema, $title); } /** * Get a table if it exists. * * @see DataTable class. * * @param $name * Unique name of the table. * * @return * A DataTable object if there is a table with this name, * FALSE if not. */ function data_get_table($name) { if ($table = _data_get_table($name)) { return $table; } return FALSE; } /** * Drop a table - use this instead of $table->drop(). */ function data_drop_table($name) { if ($table = data_get_table($name)) { $table->drop(); _data_get_table($name, NULL, NULL, TRUE); } } /** * Get schema info for all data allocated tables. * * Pull directly from database to avoid race conditions. */ function data_get_schema() { $schema = array(); $result = db_query('SELECT name, table_schema FROM {data_tables}'); while ($table = db_fetch_object($result)) { $schema[$table->name] = unserialize($table->table_schema); } return $schema; } /** * Load all data tables. */ function data_get_all_tables() { $tables = array(); if ($tables = _data_load_table()) { foreach ($tables as $table_name => $table) { if ($table = data_get_table($table_name)) { $tables[$table_name] = $table; } } } return $tables; } /** * Get a list of supported field definitions. * * This list is a sub set of Schema API data types * http://drupal.org/node/159605 * The keys are simplified handles. */ function data_get_field_definitions() { return array( 'int' => array( 'type' => 'int', 'not null' => FALSE, ), 'unsigned int' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, ), 'varchar' => array( 'type' => 'varchar', 'length' => 255, 'not null' => FALSE, ), 'text' => array( 'type' => 'text', 'not null' => FALSE, ), ); } /** * Get a definition key into a schema API type definition. * * If no type can be found, FALSE will be returned. */ function data_get_field_definition($key) { $definitions = data_get_field_definitions(); if (isset($definitions[$key])) { return $definitions[$key]; } return FALSE; } /** * Get schema API field types supported by Data module. */ function data_get_field_types() { $definitions = data_get_field_definitions(); $types = array(); foreach ($definitions as $def) { $types[$def['type']] = $def['type']; } return $types; } /** * Get a Schema API PK definition for a given field type. */ function data_get_pk_definition($name, $type) { if ($type == 'text') { return array($name, 255); } else { return $name; } } /** * Get a Schema API index definition for a given field type. * @todo: support multiple name/type combinations. */ function data_get_index_definition($name, $type) { if ($type == 'text') { return array(array($name, 255)); } else { return array($name); } } /** * Create a table name in the data namespace. * @todo: make overridable. */ function data_name($table) { return 'data_table_'. $table; } /** * Create a safe name for MySQL field or table names. * * @todo: IMPROVE. * * - make sure all unsafe characters are removed. * - filter magic words. * - test pgsql. */ function data_safe_name($name) { $map = array( '.' => '_', ':' => '', '/' => '', '-' => '_', ' ' => '_', ',' => '_', ); $simple = trim(strtolower(strip_tags($name))); // Limit length to 64 as per http://dev.mysql.com/doc/refman/5.0/en/identifiers.html $simple = substr(strtr($simple, $map), 0, 64); if (is_numeric($simple)) { // We need to escape numerics because Drupal's drupal_write_record() // does not properly escape token MYSQL names. $simple = '__num_'. $simple; } return db_escape_table($simple); } /** * Helper function to create a natural name. * underscored_name -> Underscored name */ function data_natural_name($name) { return ucfirst(strtolower(str_replace('_', ' ', $name))); } /** * Helper function to generate a schema. * * Example: * $table->create(data_build_schema($keys)); * * @todo: check for table name collisions * @todo: add type detection * @todo: add meta info handling * @todo: add primary key handling * @todo: may be add option to add a full fledged schema here? */ function data_build_schema($keys) { // Build the table definition. // Fall back to varchar if no valid type is given. $fields = $schema = array(); foreach ($keys as $k => $key) { if ($definition = data_get_field_definition($key)) { $fields[data_safe_name($k)] = $definition; } else { $fields[data_safe_name($k)] = data_get_field_definition('varchar'); } } $schema['fields'] = $fields; $schema['indexes'] = array(); return $schema; } /** * Build a full schema api field definition. * * @param $stub * Array with at least one key 'type'. */ function data_build_field_definition($stub) { $spec = array(); $spec['type'] = $stub['type']; if ($spec['type'] == 'int') { $spec['unsigned'] = empty($stub['unsigned']) ? FALSE : TRUE; } if ($spec['type'] == 'varchar') { $spec['length'] = 255; } return $spec; } /** * Export a data table. This does not export the content of a table - only its schema * and any meta information (title, name, meta...). * * @param $name * The name of the table to be exported. * * @return * Exportable code. * * Only available if ctools is installed. */ function data_export($name, $indent = '') { if (module_exists('ctools')) { ctools_include('export'); $result = ctools_export_load_object('data_tables', 'names', array($name)); if (isset($result[$name])) { return ctools_export_object('data_tables', $result[$name], $indent); } } return t('Export requires CTools http://drupal.org/project/ctools'); } /** * Internal singleton/factory function for creating a single instance of a DataTable class. * * Don't use this function directly. Call data_create_table() or data_get_table() instead. * * If a schema is given, _data_get_table() creates the table objects DB structure. * * The purpose of this function is to make sure that * * a) there is only a single DataTable object for accessing a specific DataTable. * b) there is no DataTable object that does not have an existing table. */ function _data_get_table($name, $schema = NULL, $title = NULL, $reset = FALSE) { _data_include(); static $tables; // Simple way of having a way to override the class being used. // This could be refined with a $type parameter in _data_get_table() and depending // functions. $class = variable_get('data_table_class', 'DataTable'); if ($reset) { unset($tables[$name]); } if (!isset($tables[$name])) { // Try whether we can load table, then instantiate. Object will then load itself. if (_data_load_table($name, $reset)) { $tables[$name] = new $class($name); } } if ($schema) { $tables[$name] = new $class($name, $schema, $title); } return isset($tables[$name]) ? $tables[$name] : FALSE; } /** * Loads data table info from the database. Uses CTools if available. */ function _data_load_table($name = NULL, $reset = FALSE) { if (module_exists('ctools')) { ctools_include('export'); if (empty($name)) { return ctools_export_load_object('data_tables', 'all', array(), $reset); } else { $tables = ctools_export_load_object('data_tables', 'names', array($name), $reset); if (isset($tables[$name])) { return $tables[$name]; } return FALSE; } } // If CTools is not available, load directly from DB. if (empty($name)) { $result = db_query('SELECT * FROM {data_tables}'); $tables = array(); while ($row = db_fetch_object($result)) { foreach (array('table_schema', 'meta') as $key) { $row->$key = unserialize($row->$key); } // No export type. $row->export_type = DATA_EXPORT_UNDEFINED; $tables[$row->name] = $row; } return $tables; } if ($table = db_fetch_object(db_query('SELECT * FROM {data_tables} WHERE name = "%s"', $name))) { // No export type. $table->export_type = DATA_EXPORT_UNDEFINED; return $table; } return FALSE; } /** * Starts overriding a data table by copying it from the default definition into the DB. * This function does not have any effect if called on a table that does already exist in * data_tables. */ function _data_override($name) { if (!db_result(db_query('SELECT name FROM {data_tables} WHERE name = "%s"', $name))) { if ($table = _data_load_table($name)) { drupal_write_record('data_tables', $table); } } } /** * Include class file. */ function _data_include() { static $included; if (!$included) { include drupal_get_path('module', 'data') .'/data.inc'; } $included = TRUE; }