09ca8c5a5c3042ddf516d542d0d157ae906ea995
[project/data.git] / data.module
1 <?php
2 // $Id$
3 /**
4 * @file
5 * Hooks and API functions for data module.
6 * @todo: Move helper functions into data.inc or other .inc file.
7 */
8
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
13 /**
14 * Implementation of hook_views_api().
15 */
16 function data_views_api() {
17 return array(
18 'api' => '2.0',
19 'path' => drupal_get_path('module', 'data'),
20 );
21 }
22
23 /**
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 /**
34 * Create a table.
35 *
36 * Usage:
37 * $table = data_create_table('my_table', $schema, 'My table');
38 * $table->save($data);
39 *
40 * @see DataTable class.
41 *
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'));
46 * @param $schema
47 * Schema for the table.
48 * @param $title
49 * A natural title for the table.
50 *
51 * @return
52 * A DataTable object if init could create one,
53 * FALSE if not.
54 */
55 function data_create_table($name, $schema, $title = NULL) {
56 if (_data_get_table($name, NULL, NULL, TRUE)) {
57 return FALSE;
58 }
59 return _data_get_table($name, $schema, $title);
60 }
61
62 /**
63 * Get a table if it exists.
64 *
65 * @see DataTable class.
66 *
67 * @param $name
68 * Unique name of the table.
69 *
70 * @return
71 * A DataTable object if there is a table with this name,
72 * FALSE if not.
73 */
74 function data_get_table($name) {
75 if ($table = _data_get_table($name)) {
76 return $table;
77 }
78 return FALSE;
79 }
80
81 /**
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 /**
92 * Get schema info for all data allocated tables.
93 *
94 * Pull directly from database to avoid race conditions.
95 */
96 function data_get_schema() {
97 $schema = array();
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);
101 }
102 return $schema;
103 }
104
105 /**
106 * Load all data tables.
107 */
108 function data_get_all_tables() {
109 $tables = array();
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 }
115 }
116 }
117 return $tables;
118 }
119
120 /**
121 * Get a list of supported field definitions.
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.
126 */
127 function data_get_field_definitions() {
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 /**
151 * Get a definition key into a schema API type definition.
152 *
153 * If no type can be found, FALSE will be returned.
154 */
155 function data_get_field_definition($key) {
156 $definitions = data_get_field_definitions();
157 if (isset($definitions[$key])) {
158 return $definitions[$key];
159 }
160 return FALSE;
161 }
162
163 /**
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 /**
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 /**
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.
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 /**
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) {
259 // Build the table definition.
260 // Fall back to varchar if no valid type is given.
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;
265 }
266 else {
267 $fields[data_safe_name($k)] = data_get_field_definition('varchar');
268 }
269 }
270
271 $schema['fields'] = $fields;
272 $schema['indexes'] = array();
273 return $schema;
274 }
275
276 /**
277 * Build a full schema api field definition.
278 *
279 * @param $stub
280 * Array with at least one key 'type'.
281 */
282 function data_build_field_definition($stub) {
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 /**
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 /**
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 /**
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])) {
399 // Try whether we can load table, then instantiate. Object will then load itself.
400 if (_data_load_table($name, $reset)) {
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 /**
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 /**
463 * Include class file.
464 */
465 function _data_include() {
466 static $included;
467 if (!$included) {
468 include drupal_get_path('module', 'data') .'/data.inc';
469 }
470 $included = TRUE;
471 }