| Commit | Line | Data |
|---|---|---|
| 2ace3f91 AB |
1 | <?php |
| 2 | // $Id$ | |
| 3a16de16 AB |
3 | /** |
| 4 | * @file | |
| 5 | * Hooks and API functions for data module. | |
| 12aae5b6 | 6 | * @todo: Move helper functions into data.inc or other .inc file. |
| 3a16de16 | 7 | */ |
| 2ace3f91 | 8 | |
| 72ba49d9 AB |
9 | // Constant designating an undefined export state. |
| 10 | // Used in absence of EXPORT_IN_CODE, EXPORT_IN_DATABASE | |
| 11 | define('DATA_EXPORT_UNDEFINED', 0); | |
| 12 | ||
| 2ace3f91 | 13 | /** |
| c0bdd519 AB |
14 | * Implementation of hook_views_api(). |
| 15 | */ | |
| 16 | function data_views_api() { | |
| 17 | return array( | |
| 18 | 'api' => '2.0', | |
| cff26223 | 19 | 'path' => drupal_get_path('module', 'data'), |
| 34583f68 | 20 | ); |
| c0bdd519 AB |
21 | } |
| 22 | ||
| 23 | /** | |
| 72ba49d9 AB |
24 | * Implementation of hook_features_api(). |
| 25 | */ | |
| 26 | function data_features_api() { | |
| 27 | return array( | |
| 28 | 'default_hook' => 'data_default', | |
| 29 | 'file' => drupal_get_path('module', 'data') .'/data.features.inc', | |
| 30 | ); | |
| 31 | } | |
| 32 | ||
| 33 | /** | |
| f48c6947 | 34 | * Create a table. |
| 825e0da0 AB |
35 | * |
| 36 | * Usage: | |
| f48c6947 | 37 | * $table = data_create_table('my_table', $schema, 'My table'); |
| 825e0da0 | 38 | * $table->save($data); |
| 2ace3f91 | 39 | * |
| 5a6fc132 AB |
40 | * @see DataTable class. |
| 41 | * | |
| 2ace3f91 AB |
42 | * @param $name |
| 43 | * String that identifies the data table. It is recommended to use | |
| 44 | * data_name() to generate a table name in the data namespace. For | |
| 45 | * example: $table = data_get_tabe(data_name('my_table')); | |
| f48c6947 AB |
46 | * @param $schema |
| 47 | * Schema for the table. | |
| 825e0da0 AB |
48 | * @param $title |
| 49 | * A natural title for the table. | |
| 2ace3f91 | 50 | * |
| 825e0da0 AB |
51 | * @return |
| 52 | * A DataTable object if init could create one, | |
| 53 | * FALSE if not. | |
| 2ace3f91 | 54 | */ |
| f48c6947 | 55 | function data_create_table($name, $schema, $title = NULL) { |
| 72ba49d9 | 56 | if (_data_get_table($name, NULL, NULL, TRUE)) { |
| 825e0da0 | 57 | return FALSE; |
| 2ace3f91 | 58 | } |
| f48c6947 | 59 | return _data_get_table($name, $schema, $title); |
| 2ace3f91 AB |
60 | } |
| 61 | ||
| 62 | /** | |
| 825e0da0 | 63 | * Get a table if it exists. |
| 5a6fc132 AB |
64 | * |
| 65 | * @see DataTable class. | |
| 66 | * | |
| 825e0da0 AB |
67 | * @param $name |
| 68 | * Unique name of the table. | |
| 2ace3f91 | 69 | * |
| 825e0da0 AB |
70 | * @return |
| 71 | * A DataTable object if there is a table with this name, | |
| 72 | * FALSE if not. | |
| 2ace3f91 | 73 | */ |
| 825e0da0 | 74 | function data_get_table($name) { |
| f48c6947 | 75 | if ($table = _data_get_table($name)) { |
| 825e0da0 AB |
76 | return $table; |
| 77 | } | |
| 78 | return FALSE; | |
| 2ace3f91 AB |
79 | } |
| 80 | ||
| 81 | /** | |
| f48c6947 AB |
82 | * Drop a table - use this instead of $table->drop(). |
| 83 | */ | |
| 84 | function data_drop_table($name) { | |
| 85 | if ($table = data_get_table($name)) { | |
| 86 | $table->drop(); | |
| 87 | _data_get_table($name, NULL, NULL, TRUE); | |
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 2ace3f91 | 92 | * Get schema info for all data allocated tables. |
| 72ba49d9 AB |
93 | * |
| 94 | * Pull directly from database to avoid race conditions. | |
| 2ace3f91 AB |
95 | */ |
| 96 | function data_get_schema() { | |
| 97 | $schema = array(); | |
| 72ba49d9 AB |
98 | $result = db_query('SELECT name, table_schema FROM {data_tables}'); |
| 99 | while ($table = db_fetch_object($result)) { | |
| 100 | $schema[$table->name] = unserialize($table->table_schema); | |
| 2ace3f91 AB |
101 | } |
| 102 | return $schema; | |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| 106 | * Load all data tables. | |
| 107 | */ | |
| 108 | function data_get_all_tables() { | |
| 109 | $tables = array(); | |
| 72ba49d9 AB |
110 | if ($tables = _data_load_table()) { |
| 111 | foreach ($tables as $table_name => $table) { | |
| 112 | if ($table = data_get_table($table_name)) { | |
| 113 | $tables[$table_name] = $table; | |
| 114 | } | |
| 9933042e | 115 | } |
| 2ace3f91 AB |
116 | } |
| 117 | return $tables; | |
| 118 | } | |
| 119 | ||
| 120 | /** | |
| 19aba4f3 | 121 | * Get a list of supported field definitions. |
| 2ace3f91 AB |
122 | * |
| 123 | * This list is a sub set of Schema API data types | |
| 124 | * http://drupal.org/node/159605 | |
| 125 | * The keys are simplified handles. | |
| 2ace3f91 | 126 | */ |
| 19aba4f3 | 127 | function data_get_field_definitions() { |
| 2ace3f91 AB |
128 | return array( |
| 129 | 'int' => array( | |
| 130 | 'type' => 'int', | |
| 131 | 'not null' => FALSE, | |
| 132 | ), | |
| 133 | 'unsigned int' => array( | |
| 134 | 'type' => 'int', | |
| 135 | 'unsigned' => TRUE, | |
| 136 | 'not null' => FALSE, | |
| 137 | ), | |
| 138 | 'varchar' => array( | |
| 139 | 'type' => 'varchar', | |
| 140 | 'length' => 255, | |
| 141 | 'not null' => FALSE, | |
| 142 | ), | |
| 143 | 'text' => array( | |
| 144 | 'type' => 'text', | |
| 145 | 'not null' => FALSE, | |
| 146 | ), | |
| 147 | ); | |
| 148 | } | |
| 149 | ||
| 150 | /** | |
| 19aba4f3 | 151 | * Get a definition key into a schema API type definition. |
| 2ace3f91 | 152 | * |
| 38e93719 | 153 | * If no type can be found, FALSE will be returned. |
| 2ace3f91 | 154 | */ |
| 19aba4f3 AB |
155 | function data_get_field_definition($key) { |
| 156 | $definitions = data_get_field_definitions(); | |
| 157 | if (isset($definitions[$key])) { | |
| 158 | return $definitions[$key]; | |
| 2ace3f91 | 159 | } |
| 38e93719 | 160 | return FALSE; |
| 2ace3f91 AB |
161 | } |
| 162 | ||
| 163 | /** | |
| 19aba4f3 AB |
164 | * Get schema API field types supported by Data module. |
| 165 | */ | |
| 166 | function data_get_field_types() { | |
| 167 | $definitions = data_get_field_definitions(); | |
| 168 | $types = array(); | |
| 169 | foreach ($definitions as $def) { | |
| 170 | $types[$def['type']] = $def['type']; | |
| 171 | } | |
| 172 | return $types; | |
| 173 | } | |
| 174 | ||
| 175 | /** | |
| 176 | * Get a Schema API PK definition for a given field type. | |
| 177 | */ | |
| 178 | function data_get_pk_definition($name, $type) { | |
| 179 | if ($type == 'text') { | |
| 180 | return array($name, 255); | |
| 181 | } | |
| 182 | else { | |
| 183 | return $name; | |
| 184 | } | |
| 185 | } | |
| 186 | ||
| 187 | /** | |
| 188 | * Get a Schema API index definition for a given field type. | |
| 189 | * @todo: support multiple name/type combinations. | |
| 190 | */ | |
| 191 | function data_get_index_definition($name, $type) { | |
| 192 | if ($type == 'text') { | |
| 193 | return array(array($name, 255)); | |
| 194 | } | |
| 195 | else { | |
| 196 | return array($name); | |
| 197 | } | |
| 198 | } | |
| 199 | ||
| 200 | /** | |
| 2ace3f91 AB |
201 | * Create a table name in the data namespace. |
| 202 | * @todo: make overridable. | |
| 203 | */ | |
| 204 | function data_name($table) { | |
| 205 | return 'data_table_'. $table; | |
| 206 | } | |
| 207 | ||
| 208 | /** | |
| 38e93719 AB |
209 | * Create a safe name for MySQL field or table names. |
| 210 | * | |
| 211 | * @todo: IMPROVE. | |
| 212 | * | |
| 213 | * - make sure all unsafe characters are removed. | |
| 214 | * - filter magic words. | |
| 215 | * - test pgsql. | |
| 2ace3f91 AB |
216 | */ |
| 217 | function data_safe_name($name) { | |
| 218 | $map = array( | |
| 219 | '.' => '_', | |
| 220 | ':' => '', | |
| 221 | '/' => '', | |
| 222 | '-' => '_', | |
| 223 | ' ' => '_', | |
| 224 | ',' => '_', | |
| 225 | ); | |
| 226 | $simple = trim(strtolower(strip_tags($name))); | |
| 227 | // Limit length to 64 as per http://dev.mysql.com/doc/refman/5.0/en/identifiers.html | |
| 228 | $simple = substr(strtr($simple, $map), 0, 64); | |
| 229 | ||
| 230 | if (is_numeric($simple)) { | |
| 231 | // We need to escape numerics because Drupal's drupal_write_record() | |
| 232 | // does not properly escape token MYSQL names. | |
| 233 | $simple = '__num_'. $simple; | |
| 234 | } | |
| 235 | return db_escape_table($simple); | |
| 236 | } | |
| 237 | ||
| 238 | /** | |
| 239 | * Helper function to create a natural name. | |
| 240 | * underscored_name -> Underscored name | |
| 241 | */ | |
| 242 | function data_natural_name($name) { | |
| 243 | return ucfirst(strtolower(str_replace('_', ' ', $name))); | |
| 244 | } | |
| 245 | ||
| 246 | /** | |
| 2ace3f91 AB |
247 | * Helper function to generate a schema. |
| 248 | * | |
| 249 | * Example: | |
| 250 | * $table->create(data_build_schema($keys)); | |
| 251 | * | |
| 252 | * @todo: check for table name collisions | |
| 253 | * @todo: add type detection | |
| 254 | * @todo: add meta info handling | |
| 255 | * @todo: add primary key handling | |
| 256 | * @todo: may be add option to add a full fledged schema here? | |
| 257 | */ | |
| 258 | function data_build_schema($keys) { | |
| 38e93719 AB |
259 | // Build the table definition. |
| 260 | // Fall back to varchar if no valid type is given. | |
| 19aba4f3 AB |
261 | $fields = $schema = array(); |
| 262 | foreach ($keys as $k => $key) { | |
| 263 | if ($definition = data_get_field_definition($key)) { | |
| 264 | $fields[data_safe_name($k)] = $definition; | |
| 38e93719 AB |
265 | } |
| 266 | else { | |
| 19aba4f3 | 267 | $fields[data_safe_name($k)] = data_get_field_definition('varchar'); |
| 38e93719 | 268 | } |
| 2ace3f91 | 269 | } |
| 38e93719 | 270 | |
| 2ace3f91 AB |
271 | $schema['fields'] = $fields; |
| 272 | $schema['indexes'] = array(); | |
| 273 | return $schema; | |
| 274 | } | |
| 275 | ||
| 276 | /** | |
| 19aba4f3 | 277 | * Build a full schema api field definition. |
| 2ace3f91 AB |
278 | * |
| 279 | * @param $stub | |
| 280 | * Array with at least one key 'type'. | |
| 281 | */ | |
| 19aba4f3 | 282 | function data_build_field_definition($stub) { |
| 2ace3f91 AB |
283 | $spec = array(); |
| 284 | $spec['type'] = $stub['type']; | |
| 285 | if ($spec['type'] == 'int') { | |
| 286 | $spec['unsigned'] = empty($stub['unsigned']) ? FALSE : TRUE; | |
| 287 | } | |
| 288 | if ($spec['type'] == 'varchar') { | |
| 289 | $spec['length'] = 255; | |
| 290 | } | |
| 291 | return $spec; | |
| 292 | } | |
| 293 | ||
| 294 | /** | |
| 12aae5b6 AB |
295 | * Helper for building a schema API conform index array. |
| 296 | */ | |
| 297 | function data_build_index_array($field_name, $spec) { | |
| 298 | // Default to 255 for now. | |
| 299 | if ($spec['type'] == 'text') { | |
| 300 | return array(array($field_name, 255)); | |
| 301 | } | |
| 302 | else { | |
| 303 | return array($field_name); | |
| 304 | } | |
| 305 | } | |
| 306 | ||
| 307 | /** | |
| 308 | * Helper function for adjusting a table's real schema. | |
| 309 | * @todo: this should live in schema module and should use better defined $reason keys. | |
| 310 | */ | |
| 311 | function data_alter_table($table, $field, $reason) { | |
| 312 | $schema = $table->get('table_schema'); | |
| 313 | ||
| 314 | switch ($reason) { | |
| 315 | case 'not in database': | |
| 316 | if (isset($schema['fields'][$field])) { | |
| 317 | return $table->addField($field, $schema['fields'][$field]); | |
| 318 | } | |
| 319 | return FALSE; | |
| 320 | ||
| 321 | case 'missing in database': | |
| 322 | list($type, $field) = explode(' ', $field); | |
| 323 | // @todo: support multiple keys. | |
| 324 | if ($type == 'indexes') { | |
| 325 | return $table->addIndex($field); | |
| 326 | } | |
| 327 | elseif ($type == 'unique keys') { | |
| 328 | return $table->addUniqueKey($field); | |
| 329 | } | |
| 330 | elseif ($type == 'primary key') { | |
| 331 | return $table->addPrimaryKey($schema['primary keys']); | |
| 332 | } | |
| 333 | return FALSE; | |
| 334 | ||
| 335 | case 'primary key:<br />declared': // @todo: yikes! | |
| 336 | $table->dropPrimaryKey(); | |
| 337 | return $table->changePrimaryKey($schema['primary keys']); | |
| 338 | case 'missing in schema': | |
| 339 | if ($field == 'primary key') { | |
| 340 | return $table->dropPrimaryKey(); | |
| 341 | } | |
| 342 | return FALSE; | |
| 343 | ||
| 344 | case 'unexpected column in database': | |
| 345 | return $this->dropField($field); | |
| 346 | ||
| 347 | } | |
| 348 | return FALSE; | |
| 349 | } | |
| 350 | ||
| 351 | /** | |
| 72ba49d9 AB |
352 | * Export a data table. This does not export the content of a table - only its schema |
| 353 | * and any meta information (title, name, meta...). | |
| 354 | * | |
| 355 | * @param $name | |
| 356 | * The name of the table to be exported. | |
| 357 | * | |
| 358 | * @return | |
| 359 | * Exportable code. | |
| 360 | * | |
| 361 | * Only available if ctools is installed. | |
| 362 | */ | |
| 363 | function data_export($name, $indent = '') { | |
| 364 | if (module_exists('ctools')) { | |
| 365 | ctools_include('export'); | |
| 366 | $result = ctools_export_load_object('data_tables', 'names', array($name)); | |
| 367 | if (isset($result[$name])) { | |
| 368 | return ctools_export_object('data_tables', $result[$name], $indent); | |
| 369 | } | |
| 370 | } | |
| 371 | return t('Export requires CTools http://drupal.org/project/ctools'); | |
| 372 | } | |
| 373 | ||
| 374 | /** | |
| 83dadde7 AB |
375 | * Internal singleton/factory function for creating a single instance of a DataTable class. |
| 376 | * | |
| 377 | * Don't use this function directly. Call data_create_table() or data_get_table() instead. | |
| 378 | * | |
| 379 | * If a schema is given, _data_get_table() creates the table objects DB structure. | |
| 380 | * | |
| 381 | * The purpose of this function is to make sure that | |
| 382 | * | |
| 383 | * a) there is only a single DataTable object for accessing a specific DataTable. | |
| 384 | * b) there is no DataTable object that does not have an existing table. | |
| 385 | */ | |
| 386 | function _data_get_table($name, $schema = NULL, $title = NULL, $reset = FALSE) { | |
| 387 | _data_include(); | |
| 388 | ||
| 389 | static $tables; | |
| 390 | // Simple way of having a way to override the class being used. | |
| 391 | // This could be refined with a $type parameter in _data_get_table() and depending | |
| 392 | // functions. | |
| 393 | $class = variable_get('data_table_class', 'DataTable'); | |
| 394 | if ($reset) { | |
| 395 | unset($tables[$name]); | |
| 396 | } | |
| 397 | ||
| 398 | if (!isset($tables[$name])) { | |
| 72ba49d9 AB |
399 | // Try whether we can load table, then instantiate. Object will then load itself. |
| 400 | if (_data_load_table($name, $reset)) { | |
| 83dadde7 AB |
401 | $tables[$name] = new $class($name); |
| 402 | } | |
| 403 | } | |
| 404 | if ($schema) { | |
| 405 | $tables[$name] = new $class($name, $schema, $title); | |
| 406 | } | |
| 407 | return isset($tables[$name]) ? $tables[$name] : FALSE; | |
| 408 | } | |
| 409 | ||
| 410 | /** | |
| 72ba49d9 AB |
411 | * Loads data table info from the database. Uses CTools if available. |
| 412 | */ | |
| 413 | function _data_load_table($name = NULL, $reset = FALSE) { | |
| 414 | if (module_exists('ctools')) { | |
| 415 | ctools_include('export'); | |
| 416 | if (empty($name)) { | |
| 417 | return ctools_export_load_object('data_tables', 'all', array(), $reset); | |
| 418 | } | |
| 419 | else { | |
| 420 | $tables = ctools_export_load_object('data_tables', 'names', array($name), $reset); | |
| 421 | if (isset($tables[$name])) { | |
| 422 | return $tables[$name]; | |
| 423 | } | |
| 424 | return FALSE; | |
| 425 | } | |
| 426 | } | |
| 427 | // If CTools is not available, load directly from DB. | |
| 428 | if (empty($name)) { | |
| 429 | $result = db_query('SELECT * FROM {data_tables}'); | |
| 430 | $tables = array(); | |
| 431 | while ($row = db_fetch_object($result)) { | |
| 432 | foreach (array('table_schema', 'meta') as $key) { | |
| 433 | $row->$key = unserialize($row->$key); | |
| 434 | } | |
| 435 | // No export type. | |
| 436 | $row->export_type = DATA_EXPORT_UNDEFINED; | |
| 437 | $tables[$row->name] = $row; | |
| 438 | } | |
| 439 | return $tables; | |
| 440 | } | |
| 441 | if ($table = db_fetch_object(db_query('SELECT * FROM {data_tables} WHERE name = "%s"', $name))) { | |
| 442 | // No export type. | |
| 443 | $table->export_type = DATA_EXPORT_UNDEFINED; | |
| 444 | return $table; | |
| 445 | } | |
| 446 | return FALSE; | |
| 447 | } | |
| 448 | ||
| 449 | /** | |
| 450 | * Starts overriding a data table by copying it from the default definition into the DB. | |
| 451 | * This function does not have any effect if called on a table that does already exist in | |
| 452 | * data_tables. | |
| 453 | */ | |
| 454 | function _data_override($name) { | |
| 455 | if (!db_result(db_query('SELECT name FROM {data_tables} WHERE name = "%s"', $name))) { | |
| 456 | if ($table = _data_load_table($name)) { | |
| 457 | drupal_write_record('data_tables', $table); | |
| 458 | } | |
| 459 | } | |
| 460 | } | |
| 461 | ||
| 462 | /** | |
| d826902e | 463 | * Include class file. |
| 2ace3f91 | 464 | */ |
| d826902e AB |
465 | function _data_include() { |
| 466 | static $included; | |
| 467 | if (!$included) { | |
| 468 | include drupal_get_path('module', 'data') .'/data.inc'; | |
| 825e0da0 | 469 | } |
| d826902e AB |
470 | $included = TRUE; |
| 471 | } |