Projet

Général

Profil

Paste
Télécharger (39,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / ctools / includes / export.inc @ 6e3ce7c2

1
<?php
2

    
3
/**
4
 * @file
5
 * Contains code to make it easier to have exportable objects.
6
 *
7
 * Documentation for exportable objects is contained in help/export.html.
8
 */
9

    
10
/**
11
 * A bit flag used to let us know if an object is in the database.
12
 */
13
define('EXPORT_IN_DATABASE', 0x01);
14

    
15
/**
16
 * A bit flag used to let us know if an object is a 'default' in code.
17
 */
18
define('EXPORT_IN_CODE', 0x02);
19

    
20
/**
21
 * @defgroup export_crud CRUD functions for export.
22
 * @{
23
 * export.inc supports a small number of CRUD functions that should always
24
 * work for every exportable object, no matter how complicated. These
25
 * functions allow complex objects to provide their own callbacks, but
26
 * in most cases, the default callbacks will be used.
27
 *
28
 * Note that defaults are NOT set in the $schema because it is presumed
29
 * that a module's personalized CRUD functions will already know which
30
 * $table to use and not want to clutter up the arguments with it.
31
 */
32

    
33
/**
34
 * Create a new object for the given $table.
35
 *
36
 * @param $table
37
 *   The name of the table to use to retrieve $schema values. This table
38
 *   must have an 'export' section containing data or this function
39
 *   will fail.
40
 * @param $set_defaults
41
 *   If TRUE, which is the default, then default values will be retrieved
42
 *   from schema fields and set on the object.
43
 *
44
 * @return
45
 *   The loaded object.
46
 */
47
function ctools_export_crud_new($table, $set_defaults = TRUE) {
48
  $schema = ctools_export_get_schema($table);
49
  $export = $schema['export'];
50

    
51
  if (!empty($export['create callback']) && function_exists($export['create callback'])) {
52
    return $export['create callback']($set_defaults);
53
  }
54
  else {
55
    return ctools_export_new_object($table, $set_defaults);
56
  }
57
}
58

    
59
/**
60
 * Load a single exportable object.
61
 *
62
 * @param $table
63
 *   The name of the table to use to retrieve $schema values. This table
64
 *   must have an 'export' section containing data or this function
65
 *   will fail.
66
 * @param $name
67
 *   The unique ID to load. The field for this ID will be specified by
68
 *   the export key, which normally defaults to 'name'.
69
 *
70
 * @return
71
 *   The loaded object.
72
 */
73
function ctools_export_crud_load($table, $name) {
74
  $schema = ctools_export_get_schema($table);
75
  $export = $schema['export'];
76

    
77
  if (!empty($export['load callback']) && function_exists($export['load callback'])) {
78
    return $export['load callback']($name);
79
  }
80
  else {
81
    $result = ctools_export_load_object($table, 'names', array($name));
82
    if (isset($result[$name])) {
83
      return $result[$name];
84
    }
85
  }
86
}
87

    
88
/**
89
 * Load multiple exportable objects.
90
 *
91
 * @param $table
92
 *   The name of the table to use to retrieve $schema values. This table
93
 *   must have an 'export' section containing data or this function
94
 *   will fail.
95
 * @param $names
96
 *   An array of unique IDs to load. The field for these IDs will be specified
97
 *   by the export key, which normally defaults to 'name'.
98
 *
99
 * @return
100
 *   An array of the loaded objects.
101
 */
102
function ctools_export_crud_load_multiple($table, array $names) {
103
  $schema = ctools_export_get_schema($table);
104
  $export = $schema['export'];
105

    
106
  $results = array();
107
  if (!empty($export['load multiple callback']) && function_exists($export['load multiple callback'])) {
108
    $results = $export['load multiple callback']($names);
109
  }
110
  else {
111
    $results = ctools_export_load_object($table, 'names', $names);
112
  }
113

    
114
  // Ensure no empty results are returned.
115
  return array_filter($results);
116
}
117

    
118
/**
119
 * Load all exportable objects of a given type.
120
 *
121
 * @param $table
122
 *   The name of the table to use to retrieve $schema values. This table
123
 *   must have an 'export' section containing data or this function
124
 *   will fail.
125
 * @param $reset
126
 *   If TRUE, the static cache of all objects will be flushed prior to
127
 *   loading all. This can be important on listing pages where items
128
 *   might have changed on the page load.
129
 *
130
 * @return
131
 *   An array of all loaded objects, keyed by the unique IDs of the export key.
132
 */
133
function ctools_export_crud_load_all($table, $reset = FALSE) {
134
  $schema = ctools_export_get_schema($table);
135
  if (empty($schema['export'])) {
136
    return array();
137
  }
138

    
139
  $export = $schema['export'];
140

    
141
  if ($reset) {
142
    ctools_export_load_object_reset($table);
143
  }
144

    
145
  if (!empty($export['load all callback']) && function_exists($export['load all callback'])) {
146
    return $export['load all callback']($reset);
147
  }
148
  else {
149
    return ctools_export_load_object($table, 'all');
150
  }
151
}
152

    
153
/**
154
 * Save a single exportable object.
155
 *
156
 * @param $table
157
 *   The name of the table to use to retrieve $schema values. This table
158
 *   must have an 'export' section containing data or this function
159
 *   will fail.
160
 * @param $object
161
 *   The fully populated object to save.
162
 *
163
 * @return
164
 *   Failure to write a record will return FALSE. Otherwise SAVED_NEW or
165
 *   SAVED_UPDATED is returned depending on the operation performed. The
166
 *   $object parameter contains values for any serial fields defined by the $table
167
 */
168
function ctools_export_crud_save($table, &$object) {
169
  $schema = ctools_export_get_schema($table);
170
  $export = $schema['export'];
171

    
172
  if (!empty($export['save callback']) && function_exists($export['save callback'])) {
173
    return $export['save callback']($object);
174
  }
175
  else {
176
    // Objects should have a serial primary key. If not, simply fail to write.
177
    if (empty($export['primary key'])) {
178
      return FALSE;
179
    }
180

    
181
    $key = $export['primary key'];
182
    if ($object->export_type & EXPORT_IN_DATABASE) {
183
      // Existing record.
184
      $update = array($key);
185
    }
186
    else {
187
      // New record.
188
      $update = array();
189
      $object->export_type = EXPORT_IN_DATABASE;
190
    }
191
    return drupal_write_record($table, $object, $update);
192
  }
193
}
194

    
195
/**
196
 * Delete a single exportable object.
197
 *
198
 * This only deletes from the database, which means that if an item is in
199
 * code, then this is actually a revert.
200
 *
201
 * @param $table
202
 *   The name of the table to use to retrieve $schema values. This table
203
 *   must have an 'export' section containing data or this function
204
 *   will fail.
205
 * @param $object
206
 *   The fully populated object to delete, or the export key.
207
 */
208
function ctools_export_crud_delete($table, $object) {
209
  $schema = ctools_export_get_schema($table);
210
  $export = $schema['export'];
211

    
212
  if (!empty($export['delete callback']) && function_exists($export['delete callback'])) {
213
    return $export['delete callback']($object);
214
  }
215
  else {
216
    // If we were sent an object, get the export key from it. Otherwise
217
    // assume we were sent the export key.
218
    $value = is_object($object) ? $object->{$export['key']} : $object;
219
    db_delete($table)
220
      ->condition($export['key'], $value)
221
      ->execute();
222
  }
223
}
224

    
225
/**
226
 * Get the exported code of a single exportable object.
227
 *
228
 * @param $table
229
 *   The name of the table to use to retrieve $schema values. This table
230
 *   must have an 'export' section containing data or this function
231
 *   will fail.
232
 * @param $object
233
 *   The fully populated object to delete, or the export key.
234
 * @param $indent
235
 *   Any indentation to apply to the code, in case this object is embedded
236
 *   into another, for example.
237
 *
238
 * @return
239
 *   A string containing the executable export of the object.
240
 */
241
function ctools_export_crud_export($table, $object, $indent = '') {
242
  $schema = ctools_export_get_schema($table);
243
  $export = $schema['export'];
244

    
245
  if (!empty($export['export callback']) && function_exists($export['export callback'])) {
246
    return $export['export callback']($object, $indent);
247
  }
248
  else {
249
    return ctools_export_object($table, $object, $indent);
250
  }
251
}
252

    
253
/**
254
 * Turn exported code into an object.
255
 *
256
 * Note: If the code is poorly formed, this could crash and there is no
257
 * way to prevent this.
258
 *
259
 * @param $table
260
 *   The name of the table to use to retrieve $schema values. This table
261
 *   must have an 'export' section containing data or this function
262
 *   will fail.
263
 * @param $code
264
 *   The code to eval to create the object.
265
 *
266
 * @return
267
 *   An object created from the export. This object will NOT have been saved
268
 *   to the database. In the case of failure, a string containing all errors
269
 *   that the system was able to determine.
270
 */
271
function ctools_export_crud_import($table, $code) {
272
  $schema = ctools_export_get_schema($table);
273
  $export = $schema['export'];
274

    
275
  if (!empty($export['import callback']) && function_exists($export['import callback'])) {
276
    return $export['import callback']($code);
277
  }
278
  else {
279
    ob_start();
280
    eval($code);
281
    ob_end_clean();
282

    
283
    if (empty(${$export['identifier']})) {
284
      $errors = ob_get_contents();
285
      if (empty($errors)) {
286
        $errors = t('No item found.');
287
      }
288
      return $errors;
289
    }
290

    
291
    $item = ${$export['identifier']};
292

    
293
    // Set these defaults just the same way that ctools_export_new_object sets
294
    // them.
295
    $item->export_type = NULL;
296
    $item->{$export['export type string']} = t('Local');
297

    
298
    return $item;
299
  }
300
}
301

    
302
/**
303
 * Change the status of a certain object.
304
 *
305
 * @param $table
306
 *   The name of the table to use to enable a certain object. This table
307
 *   must have an 'export' section containing data or this function
308
 *   will fail.
309
 * @param $object
310
 *   The fully populated object to enable, or the machine readable name.
311
 * @param $status
312
 *   The status, in this case, is whether or not it is 'disabled'.
313
 */
314
function ctools_export_crud_set_status($table, $object, $status) {
315
  $schema = ctools_export_get_schema($table);
316
  $export = $schema['export'];
317

    
318
  if (!empty($export['status callback']) && function_exists($export['status callback'])) {
319
    $export['status callback']($object, $status);
320
  }
321
  else {
322
    if (is_object($object)) {
323
      ctools_export_set_object_status($object, $status);
324
    }
325
    else {
326
      ctools_export_set_status($table, $object, $status);
327
    }
328
  }
329

    
330
}
331

    
332
/**
333
 * Enable a certain object.
334
 *
335
 * @param $table
336
 *   The name of the table to use to enable a certain object. This table
337
 *   must have an 'export' section containing data or this function
338
 *   will fail.
339
 * @param $object
340
 *   The fully populated object to enable, or the machine readable name.
341
 */
342
function ctools_export_crud_enable($table, $object) {
343
  return ctools_export_crud_set_status($table, $object, FALSE);
344
}
345

    
346
/**
347
 * Disable a certain object.
348
 *
349
 * @param $table
350
 *   The name of the table to use to disable a certain object. This table
351
 *   must have an 'export' section containing data or this function
352
 *   will fail.
353
 * @param $object
354
 *   The fully populated object to disable, or the machine readable name.
355
 */
356
function ctools_export_crud_disable($table, $object) {
357
  return ctools_export_crud_set_status($table, $object, TRUE);
358
}
359

    
360
/**
361
 * @}
362
 */
363

    
364
/**
365
 * Load some number of exportable objects.
366
 *
367
 * This function will cache the objects, load subsidiary objects if necessary,
368
 * check default objects in code and properly set them up. It will cache
369
 * the results so that multiple calls to load the same objects
370
 * will not cause problems.
371
 *
372
 * It attempts to reduce, as much as possible, the number of queries
373
 * involved.
374
 *
375
 * @param $table
376
 *   The name of the table to be loaded from. Data is expected to be in the
377
 *   schema to make all this work.
378
 * @param $type
379
 *   A string to notify the loader what the argument is
380
 *   - all: load all items. This is the default. $args is unused.
381
 *   - names: $args will be an array of specific named objects to load.
382
 *   - conditions: $args will be a keyed array of conditions. The conditions
383
 *       must be in the schema for this table or errors will result.
384
 * @param $args
385
 *   An array of arguments whose actual use is defined by the $type argument.
386
 */
387
function ctools_export_load_object($table, $type = 'all', $args = array()) {
388
  $cache = &drupal_static(__FUNCTION__);
389
  $cache_table_exists = &drupal_static(__FUNCTION__ . '_table_exists', array());
390
  $cached_database = &drupal_static('ctools_export_load_object_all');
391

    
392
  if (!array_key_exists($table, $cache_table_exists)) {
393
    $cache_table_exists[$table] = db_table_exists($table);
394
  }
395

    
396
  $schema = ctools_export_get_schema($table);
397
  if (empty($schema) || !$cache_table_exists[$table]) {
398
    return array();
399
  }
400

    
401
  $export = $schema['export'];
402

    
403
  if (!isset($cache[$table])) {
404
    $cache[$table] = array();
405
  }
406

    
407
  // If fetching all and cached all, we've done so and we are finished.
408
  if ($type == 'all' && !empty($cached_database[$table])) {
409
    return $cache[$table];
410
  }
411

    
412
  $return = array();
413

    
414
  // Don't load anything we've already cached.
415
  if ($type == 'names' && !empty($args)) {
416
    foreach ($args as $id => $name) {
417
      if (isset($cache[$table][$name])) {
418
        $return[$name] = $cache[$table][$name];
419
        unset($args[$id]);
420
      }
421
    }
422

    
423
    // If nothing left to load, return the result.
424
    if (empty($args)) {
425
      return $return;
426
    }
427
  }
428

    
429
  // Build the query.
430
  $query = db_select($table, 't__0')->fields('t__0');
431
  $alias_count = 1;
432
  if (!empty($schema['join'])) {
433
    foreach ($schema['join'] as $join_key => $join) {
434
      if ($join_schema = drupal_get_schema($join['table'])) {
435
        $query->join($join['table'], 't__' . $alias_count, 't__0.' . $join['left_key'] . ' = ' . 't__' . $alias_count . '.' . $join['right_key']);
436
        $query->fields('t__' . $alias_count);
437
        $alias_count++;
438

    
439
        // Allow joining tables to alter the query through a callback.
440
        if (isset($join['callback']) && function_exists($join['callback'])) {
441
          $join['callback']($query, $schema, $join_schema);
442
        }
443
      }
444
    }
445
  }
446

    
447
  $conditions = array();
448
  $query_args = array();
449

    
450
  // If they passed in names, add them to the query.
451
  if ($type == 'names') {
452
    $query->condition($export['key'], $args, 'IN');
453
  }
454
  elseif ($type == 'conditions') {
455
    foreach ($args as $key => $value) {
456
      if (isset($schema['fields'][$key])) {
457
        $query->condition($key, $value);
458
      }
459
    }
460
  }
461

    
462
  $result = $query->execute();
463

    
464
  $status = variable_get($export['status'], array());
465
  // Unpack the results of the query onto objects and cache them.
466
  foreach ($result as $data) {
467
    if (isset($schema['export']['object factory']) && function_exists($schema['export']['object factory'])) {
468
      $object = $schema['export']['object factory']($schema, $data);
469
    }
470
    else {
471
      $object = _ctools_export_unpack_object($schema, $data, $export['object']);
472
    }
473
    $object->table = $table;
474
    $object->{$export['export type string']} = t('Normal');
475
    $object->export_type = EXPORT_IN_DATABASE;
476
    // Determine if default object is enabled or disabled.
477
    if (isset($status[$object->{$export['key']}])) {
478
      $object->disabled = $status[$object->{$export['key']}];
479
    }
480

    
481
    $cache[$table][$object->{$export['key']}] = $object;
482
    if ($type == 'conditions') {
483
      $return[$object->{$export['key']}] = $object;
484
    }
485
  }
486

    
487
  // Load subrecords.
488
  if (isset($export['subrecords callback']) && function_exists($export['subrecords callback'])) {
489
    $export['subrecords callback']($cache[$table]);
490
  }
491

    
492
  if ($type == 'names' && !empty($args) && !empty($export['cache defaults'])) {
493
    $defaults = _ctools_export_get_some_defaults($table, $export, $args);
494
  }
495
  else {
496
    $defaults = _ctools_export_get_defaults($table, $export);
497
  }
498

    
499
  if ($defaults) {
500
    foreach ($defaults as $object) {
501
      if ($type == 'conditions') {
502
        // If this does not match all of our conditions, skip it.
503
        foreach ($args as $key => $value) {
504
          if (!isset($object->$key)) {
505
            continue 2;
506
          }
507
          if (is_array($value)) {
508
            if (!in_array($object->$key, $value)) {
509
              continue 2;
510
            }
511
          }
512
          elseif ($object->$key != $value) {
513
            continue 2;
514
          }
515
        }
516
      }
517
      elseif ($type == 'names') {
518
        if (!in_array($object->{$export['key']}, $args)) {
519
          continue;
520
        }
521
      }
522

    
523
      // Determine if default object is enabled or disabled.
524
      if (isset($status[$object->{$export['key']}])) {
525
        $object->disabled = $status[$object->{$export['key']}];
526
      }
527

    
528
      if (!empty($cache[$table][$object->{$export['key']}])) {
529
        $cache[$table][$object->{$export['key']}]->{$export['export type string']} = t('Overridden');
530
        $cache[$table][$object->{$export['key']}]->export_type |= EXPORT_IN_CODE;
531
        $cache[$table][$object->{$export['key']}]->export_module = isset($object->export_module) ? $object->export_module : NULL;
532
        if ($type == 'conditions') {
533
          $return[$object->{$export['key']}] = $cache[$table][$object->{$export['key']}];
534
        }
535
      }
536
      else {
537
        $object->{$export['export type string']} = t('Default');
538
        $object->export_type = EXPORT_IN_CODE;
539
        $object->in_code_only = TRUE;
540
        $object->table = $table;
541

    
542
        $cache[$table][$object->{$export['key']}] = $object;
543
        if ($type == 'conditions') {
544
          $return[$object->{$export['key']}] = $object;
545
        }
546
      }
547
    }
548
  }
549

    
550
  // If fetching all, we've done so and we are finished.
551
  if ($type == 'all') {
552
    $cached_database[$table] = TRUE;
553
    return $cache[$table];
554
  }
555

    
556
  if ($type == 'names') {
557
    foreach ($args as $name) {
558
      if (isset($cache[$table][$name])) {
559
        $return[$name] = $cache[$table][$name];
560
      }
561
    }
562
  }
563

    
564
  return $return;
565
}
566

    
567
/**
568
 * Reset all static caches in ctools_export_load_object() or static caches for
569
 * a given table in ctools_export_load_object().
570
 *
571
 * @param $table
572
 *   String that is the name of a table. If not defined, all static caches in
573
 *   ctools_export_load_object() will be reset.
574
 */
575
function ctools_export_load_object_reset($table = NULL) {
576
  // Reset plugin cache to make sure new include files are picked up.
577
  ctools_include('plugins');
578
  ctools_get_plugins_reset();
579
  if (empty($table)) {
580
    drupal_static_reset('ctools_export_load_object');
581
    drupal_static_reset('ctools_export_load_object_all');
582
    drupal_static_reset('_ctools_export_get_defaults');
583
  }
584
  else {
585
    $cache = &drupal_static('ctools_export_load_object');
586
    $cached_database = &drupal_static('ctools_export_load_object_all');
587
    $cached_defaults = &drupal_static('_ctools_export_get_defaults');
588
    unset($cache[$table]);
589
    unset($cached_database[$table]);
590
    unset($cached_defaults[$table]);
591
  }
592
}
593

    
594
/**
595
 * Get the default version of an object, if it exists.
596
 *
597
 * This function doesn't care if an object is in the database or not and
598
 * does not check. This means that export_type could appear to be incorrect,
599
 * because a version could exist in the database. However, it's not
600
 * incorrect for this function as it is *only* used for the default
601
 * in code version.
602
 */
603
function ctools_get_default_object($table, $name) {
604
  $schema = ctools_export_get_schema($table);
605
  $export = $schema['export'];
606

    
607
  if (!$export['default hook']) {
608
    return;
609
  }
610

    
611
  // Try to load individually from cache if this cache is enabled.
612
  if (!empty($export['cache defaults'])) {
613
    $defaults = _ctools_export_get_some_defaults($table, $export, array($name));
614
  }
615
  else {
616
    $defaults = _ctools_export_get_defaults($table, $export);
617
  }
618

    
619
  $status = variable_get($export['status'], array());
620

    
621
  if (!isset($defaults[$name])) {
622
    return;
623
  }
624

    
625
  $object = $defaults[$name];
626

    
627
  // Determine if default object is enabled or disabled.
628
  if (isset($status[$object->{$export['key']}])) {
629
    $object->disabled = $status[$object->{$export['key']}];
630
  }
631

    
632
  $object->{$export['export type string']} = t('Default');
633
  $object->export_type = EXPORT_IN_CODE;
634
  $object->in_code_only = TRUE;
635

    
636
  return $object;
637
}
638

    
639
/**
640
 * Get export object defaults.
641
 *
642
 * Call the hook to get all default objects of the given type from the
643
 * export. If configured properly, this could include loading up an API
644
 * to get default objects.
645
 *
646
 * @param string $table
647
 *   The name of the table to be loaded. Data is expected to be in the
648
 *   schema to make all this work.
649
 * @param array $export
650
 *   The export definition from the table's hook_schema() definition.
651
 */
652
function _ctools_export_get_defaults($table, array $export) {
653
  $cache = &drupal_static(__FUNCTION__, array());
654

    
655
  // If defaults may be cached, first see if we can load from cache.
656
  if (!isset($cache[$table]) && !empty($export['cache defaults'])) {
657
    $cache[$table] = _ctools_export_get_defaults_from_cache($table, $export);
658
  }
659

    
660
  if (!isset($cache[$table])) {
661
    // If we're caching, attempt to get a lock. We will wait a short time
662
    // on the lock, but not too long, because it's better to just rebuild
663
    // and throw away results than wait too long on a lock.
664
    if (!empty($export['cache defaults'])) {
665
      for ($counter = 0; !($lock = lock_acquire('ctools_export:' . $table)) && $counter > 5; $counter++) {
666
        lock_wait('ctools_export:' . $table, 1);
667
        ++$counter;
668
      }
669
    }
670

    
671
    $cache[$table] = array();
672

    
673
    if ($export['default hook']) {
674
      if (!empty($export['api'])) {
675
        ctools_include('plugins');
676
        $info = ctools_plugin_api_include($export['api']['owner'], $export['api']['api'],
677
          $export['api']['minimum_version'], $export['api']['current_version']);
678
        $modules = array_keys($info);
679
      }
680
      else {
681
        $modules = module_implements($export['default hook']);
682
      }
683

    
684
      foreach ($modules as $module) {
685
        $function = $module . '_' . $export['default hook'];
686
        if (function_exists($function)) {
687
          foreach ((array) $function($export) as $name => $object) {
688
            // Record the module that provides this exportable.
689
            $object->export_module = $module;
690

    
691
            if (empty($export['api'])) {
692
              $cache[$table][$name] = $object;
693
            }
694
            else {
695
              // If version checking is enabled, ensure that the object can be
696
              // used.
697
              if (isset($object->api_version) &&
698
                version_compare($object->api_version, $export['api']['minimum_version']) >= 0 &&
699
                version_compare($object->api_version, $export['api']['current_version']) <= 0) {
700
                $cache[$table][$name] = $object;
701
              }
702
            }
703
          }
704
        }
705
      }
706

    
707
      drupal_alter($export['default hook'], $cache[$table]);
708

    
709
      // If we acquired a lock earlier, cache the results and release the
710
      // lock.
711
      if (!empty($lock)) {
712
        // Cache the index.
713
        $index = array_keys($cache[$table]);
714
        cache_set('ctools_export_index:' . $table, $index, $export['default cache bin']);
715

    
716
        // Cache each object.
717
        foreach ($cache[$table] as $name => $object) {
718
          cache_set('ctools_export:' . $table . ':' . $name, $object, $export['default cache bin']);
719
        }
720
        lock_release('ctools_export:' . $table);
721
      }
722
    }
723
  }
724

    
725
  return $cache[$table];
726
}
727

    
728
/**
729
 * Attempt to load default objects from cache.
730
 *
731
 * We can be instructed to cache default objects by the schema. If so
732
 * we cache them as an index which is a list of all default objects, and
733
 * then each default object is cached individually.
734
 *
735
 * @return Either an array of cached objects, or NULL indicating a cache
736
 *   rebuild is necessary.
737
 */
738
function _ctools_export_get_defaults_from_cache($table, $export) {
739
  $data = cache_get('ctools_export_index:' . $table, $export['default cache bin']);
740
  if (!$data || !is_array($data->data)) {
741
    return;
742
  }
743

    
744
  // This is the perfectly valid case where there are no default objects,
745
  // and we have cached this state.
746
  if (empty($data->data)) {
747
    return array();
748
  }
749

    
750
  $keys = array();
751
  foreach ($data->data as $name) {
752
    $keys[] = 'ctools_export:' . $table . ':' . $name;
753
  }
754

    
755
  $data = cache_get_multiple($keys, $export['default cache bin']);
756

    
757
  // If any of our indexed keys missed, then we have a fail and we need to
758
  // rebuild.
759
  if (!empty($keys)) {
760
    return;
761
  }
762

    
763
  // Now, translate the returned cache objects to actual objects.
764
  $cache = array();
765
  foreach ($data as $cached_object) {
766
    $cache[$cached_object->data->{$export['key']}] = $cached_object->data;
767
  }
768

    
769
  return $cache;
770
}
771

    
772
/**
773
 * Get a limited number of default objects.
774
 *
775
 * This attempts to load the objects directly from cache. If it cannot,
776
 * the cache is rebuilt. This does not disturb the general get defaults
777
 * from cache object.
778
 *
779
 * This function should ONLY be called if default caching is enabled.
780
 * It does not check, it is assumed the caller has already done so.
781
 */
782
function _ctools_export_get_some_defaults($table, $export, $names) {
783
  foreach ($names as $name) {
784
    $keys[] = 'ctools_export:' . $table . ':' . $name;
785
  }
786

    
787
  $data = cache_get_multiple($keys, $export['default cache bin']);
788

    
789
  // Cache hits remove the $key from $keys by reference. Cache
790
  // misses do not. A cache miss indicates we may have to rebuild.
791
  if (!empty($keys)) {
792
    return _ctools_export_get_defaults($table, $export);
793
  }
794

    
795
  // Now, translate the returned cache objects to actual objects.
796
  $cache = array();
797
  foreach ($data as $cached_object) {
798
    $cache[$cached_object->data->{$export['key']}] = $cached_object->data;
799
  }
800

    
801
  return $cache;
802
}
803

    
804
/**
805
 * Unpack data loaded from the database onto an object.
806
 *
807
 * @param $schema
808
 *   The schema from drupal_get_schema().
809
 * @param $data
810
 *   The data as loaded from the database.
811
 * @param $object
812
 *   If an object, data will be unpacked onto it. If a string
813
 *   an object of that type will be created.
814
 */
815
function _ctools_export_unpack_object($schema, $data, $object = 'stdClass') {
816
  if (is_string($object)) {
817
    if (class_exists($object)) {
818
      $object = new $object();
819
    }
820
    else {
821
      $object = new stdClass();
822
    }
823
  }
824

    
825
  // Go through our schema and build correlations.
826
  foreach ($schema['fields'] as $field => $info) {
827
    if (isset($data->$field)) {
828
      $object->$field = empty($info['serialize']) ? $data->$field : unserialize($data->$field);
829
    }
830
    else {
831
      $object->$field = NULL;
832
    }
833
  }
834

    
835
  if (isset($schema['join'])) {
836
    foreach ($schema['join'] as $join_key => $join) {
837
      $join_schema = ctools_export_get_schema($join['table']);
838
      if (!empty($join['load'])) {
839
        foreach ($join['load'] as $field) {
840
          $info = $join_schema['fields'][$field];
841
          $object->$field = empty($info['serialize']) ? $data->$field : unserialize($data->$field);
842
        }
843
      }
844
    }
845
  }
846

    
847
  return $object;
848
}
849

    
850
/**
851
 * Unpack data loaded from the database onto an object.
852
 *
853
 * @param $table
854
 *   The name of the table this object represents.
855
 * @param $data
856
 *   The data as loaded from the database.
857
 */
858
function ctools_export_unpack_object($table, $data) {
859
  $schema = ctools_export_get_schema($table);
860
  return _ctools_export_unpack_object($schema, $data, $schema['export']['object']);
861
}
862

    
863
/**
864
 * Export a field.
865
 *
866
 * This is a replacement for var_export(), allowing us to more nicely
867
 * format exports. It will recurse down into arrays and will try to
868
 * properly export bools when it can, though PHP has a hard time with
869
 * this since they often end up as strings or ints.
870
 */
871
function ctools_var_export($var, $prefix = '') {
872
  if (is_array($var)) {
873
    if (empty($var)) {
874
      $output = 'array()';
875
    }
876
    else {
877
      $output = "array(\n";
878
      ksort($var);
879
      foreach ($var as $key => $value) {
880
        $output .= $prefix . "  " . ctools_var_export($key) . " => " . ctools_var_export($value, $prefix . '  ') . ",\n";
881
      }
882
      $output .= $prefix . ')';
883
    }
884
  }
885
  elseif (is_object($var) && get_class($var) === 'stdClass') {
886
    // var_export() will export stdClass objects using an undefined
887
    // magic method __set_state() leaving the export broken. This
888
    // workaround avoids this by casting the object as an array for
889
    // export and casting it back to an object when evaluated.
890
    $output = '(object) ' . ctools_var_export((array) $var, $prefix);
891
  }
892
  elseif (is_bool($var)) {
893
    $output = $var ? 'TRUE' : 'FALSE';
894
  }
895
  else {
896
    $output = var_export($var, TRUE);
897
  }
898

    
899
  return $output;
900
}
901

    
902
/**
903
 * Export an object into code.
904
 */
905
function ctools_export_object($table, $object, $indent = '', $identifier = NULL, $additions = array(), $additions2 = array()) {
906
  $schema = ctools_export_get_schema($table);
907
  if (!isset($identifier)) {
908
    $identifier = $schema['export']['identifier'];
909
  }
910

    
911
  $output = $indent . '$' . $identifier . ' = new ' . get_class($object) . "();\n";
912

    
913
  if ($schema['export']['can disable']) {
914
    $disabled = !isset($object->disabled) || $object->disabled != TRUE ? 'FALSE' : 'TRUE';
915
    $output .= $indent . '$' . $identifier . '->disabled = ' . $disabled . '; /* Edit this to true to make a default ' . $identifier . ' disabled initially */' . "\n";
916
  }
917
  if (!empty($schema['export']['api']['current_version'])) {
918
    $output .= $indent . '$' . $identifier . '->api_version = ' . $schema['export']['api']['current_version'] . ";\n";
919
  }
920

    
921
  // Put top additions here:
922
  foreach ($additions as $field => $value) {
923
    $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
924
  }
925

    
926
  $fields = $schema['fields'];
927
  if (!empty($schema['join'])) {
928
    foreach ($schema['join'] as $join) {
929
      if (!empty($join['load'])) {
930
        foreach ($join['load'] as $join_field) {
931
          $fields[$join_field] = $join['fields'][$join_field];
932
        }
933
      }
934
    }
935
  }
936

    
937
  // Go through our schema and joined tables and build correlations.
938
  foreach ($fields as $field => $info) {
939
    if (!empty($info['no export'])) {
940
      continue;
941
    }
942
    if (!isset($object->$field)) {
943
      if (isset($info['default'])) {
944
        $object->$field = $info['default'];
945
      }
946
      else {
947
        $object->$field = '';
948
      }
949
    }
950

    
951
    // Note: This is the *field* export callback, not the table one!
952
    if (!empty($info['export callback']) && function_exists($info['export callback'])) {
953
      $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . $info['export callback']($object, $field, $object->$field, $indent) . ";\n";
954
    }
955
    else {
956
      $value = $object->$field;
957
      if ($info['type'] == 'int') {
958
        if (isset($info['size']) && $info['size'] == 'tiny') {
959
          $info['boolean'] = (!isset($info['boolean'])) ? $schema['export']['boolean'] : $info['boolean'];
960
          $value = ($info['boolean']) ? (bool) $value : (int) $value;
961
        }
962
        else {
963
          $value = (int) $value;
964
        }
965
      }
966

    
967
      $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
968
    }
969
  }
970

    
971
  // And bottom additions here.
972
  foreach ($additions2 as $field => $value) {
973
    $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
974
  }
975

    
976
  return $output;
977
}
978

    
979
/**
980
 * Get the schema for a given table.
981
 *
982
 * This looks for data the export subsystem needs and applies defaults so
983
 * that it's easily available.
984
 */
985
function ctools_export_get_schema($table) {
986
  static $drupal_static_fast;
987
  if (!isset($drupal_static_fast)) {
988
    $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
989
  }
990
  $cache = &$drupal_static_fast['cache'];
991

    
992
  if (empty($cache[$table])) {
993
    $schema = drupal_get_schema($table);
994

    
995
    // If our schema isn't loaded, it's possible we're in a state where it
996
    // simply hasn't been cached. If we've been asked, let's force the
997
    // issue.
998
    if (!$schema || empty($schema['export'])) {
999
      // Force a schema reset:
1000
      $schema = drupal_get_schema($table, TRUE);
1001
    }
1002

    
1003
    if (!isset($schema['export'])) {
1004
      return array();
1005
    }
1006

    
1007
    if (empty($schema['module'])) {
1008
      return array();
1009
    }
1010

    
1011
    // Add some defaults.
1012
    $schema['export'] += array(
1013
      'key' => 'name',
1014
      'key name' => 'Name',
1015
      'object' => 'stdClass',
1016
      'status' => 'default_' . $table,
1017
      'default hook' => 'default_' . $table,
1018
      'can disable' => TRUE,
1019
      'identifier' => $table,
1020
      'primary key' => !empty($schema['primary key']) ? $schema['primary key'][0] : '',
1021
      'bulk export' => TRUE,
1022
      'list callback' => "$schema[module]_{$table}_list",
1023
      'to hook code callback' => "$schema[module]_{$table}_to_hook_code",
1024
      'cache defaults' => FALSE,
1025
      'default cache bin' => 'cache',
1026
      'export type string' => 'type',
1027
      'boolean' => TRUE,
1028
    );
1029

    
1030
    // If the export definition doesn't have the "primary key" then the CRUD
1031
    // save callback won't work.
1032
    if (empty($schema['export']['primary key']) && user_access('administer site configuration')) {
1033
      drupal_set_message(t('The export definition of @table is missing the "primary key" property.', array('@table' => $table)), 'error');
1034
    }
1035

    
1036
    // Notes:
1037
    // The following callbacks may be defined to override default behavior
1038
    // when using CRUD functions:
1039
    //
1040
    // create callback
1041
    // load callback
1042
    // load multiple callback
1043
    // load all callback
1044
    // save callback
1045
    // delete callback
1046
    // export callback
1047
    // import callback
1048
    //
1049
    // See the appropriate ctools_export_crud function for details on what
1050
    // arguments these callbacks should accept. Please do not call these
1051
    // directly, always use the ctools_export_crud_* wrappers to ensure
1052
    // that default implementations are honored.
1053
    $cache[$table] = $schema;
1054
  }
1055

    
1056
  return $cache[$table];
1057
}
1058

    
1059
/**
1060
 * Gets the schemas for all tables with ctools object metadata.
1061
 */
1062
function ctools_export_get_schemas($for_export = FALSE) {
1063
  $export_tables = &drupal_static(__FUNCTION__);
1064
  if (is_null($export_tables)) {
1065
    $export_tables = array();
1066
    $schemas = drupal_get_schema();
1067
    foreach ($schemas as $table => $schema) {
1068
      if (!isset($schema['export'])) {
1069
        unset($schemas[$table]);
1070
        continue;
1071
      }
1072
      $export_tables[$table] = ctools_export_get_schema($table);
1073
    }
1074
  }
1075
  return $for_export ? array_filter($export_tables, '_ctools_export_filter_export_tables') : $export_tables;
1076
}
1077

    
1078
function _ctools_export_filter_export_tables($schema) {
1079
  return !empty($schema['export']['bulk export']);
1080
}
1081

    
1082
function ctools_export_get_schemas_by_module($modules = array(), $for_export = FALSE) {
1083
  $export_tables = array();
1084
  $list = ctools_export_get_schemas($for_export);
1085
  foreach ($list as $table => $schema) {
1086
    $export_tables[$schema['module']][$table] = $schema;
1087
  }
1088
  return empty($modules) ? $export_tables : array_keys($export_tables, $modules);
1089
}
1090

    
1091
/**
1092
 * Set the status of a default $object as a variable.
1093
 *
1094
 * The status, in this case, is whether or not it is 'disabled'.
1095
 * This function does not check to make sure $object actually
1096
 * exists.
1097
 */
1098
function ctools_export_set_status($table, $name, $new_status = TRUE) {
1099
  $schema = ctools_export_get_schema($table);
1100
  $status = variable_get($schema['export']['status'], array());
1101

    
1102
  $status[$name] = $new_status;
1103
  variable_set($schema['export']['status'], $status);
1104
}
1105

    
1106
/**
1107
 * Set the status of a default $object as a variable.
1108
 *
1109
 * This is more efficient than ctools_export_set_status because it
1110
 * will actually unset the variable entirely if it's not necessary,
1111
 * this saving a bit of space.
1112
 */
1113
function ctools_export_set_object_status($object, $new_status = TRUE) {
1114
  $table = $object->table;
1115
  $schema = ctools_export_get_schema($table);
1116
  $export = $schema['export'];
1117
  $status = variable_get($export['status'], array());
1118

    
1119
  // Compare.
1120
  if (!$new_status && $object->export_type & EXPORT_IN_DATABASE) {
1121
    unset($status[$object->{$export['key']}]);
1122
  }
1123
  else {
1124
    $status[$object->{$export['key']}] = $new_status;
1125
  }
1126

    
1127
  variable_set($export['status'], $status);
1128
}
1129

    
1130
/**
1131
 * Provide a form for displaying an export.
1132
 *
1133
 * This is a simple form that should be invoked like this:
1134
 * @code
1135
 *   $output = drupal_get_form('ctools_export_form', $code, $object_title);
1136
 * @endcode
1137
 */
1138
function ctools_export_form($form, &$form_state, $code, $title = '') {
1139
  $lines = substr_count($code, "\n");
1140
  $form['code'] = array(
1141
    '#type' => 'textarea',
1142
    '#title' => $title,
1143
    '#default_value' => $code,
1144
    '#rows' => $lines,
1145
  );
1146

    
1147
  return $form;
1148
}
1149

    
1150
/**
1151
 * Create a new object based upon schema values.
1152
 *
1153
 * Because 'default' has ambiguous meaning on some fields, we will actually
1154
 * use 'object default' to fill in default values if default is not set
1155
 * That's a little safer to use as it won't cause weird database default
1156
 * situations.
1157
 */
1158
function ctools_export_new_object($table, $set_defaults = TRUE) {
1159
  $schema = ctools_export_get_schema($table);
1160
  $export = $schema['export'];
1161

    
1162
  $object = new $export['object']();
1163
  foreach ($schema['fields'] as $field => $info) {
1164
    if (isset($info['object default'])) {
1165
      $object->$field = $info['object default'];
1166
    }
1167
    elseif (isset($info['default'])) {
1168
      $object->$field = $info['default'];
1169
    }
1170
    else {
1171
      $object->$field = NULL;
1172
    }
1173
  }
1174

    
1175
  if ($set_defaults) {
1176
    // Set some defaults so this data always exists.
1177
    // We don't set the export_type property here, as this object is not saved
1178
    // yet. We do give it NULL so we don't generate notices trying to read it.
1179
    $object->export_type = NULL;
1180
    $object->{$export['export type string']} = t('Local');
1181
  }
1182
  return $object;
1183
}
1184

    
1185
/**
1186
 * Convert a group of objects to code based upon input and return this as a larger
1187
 * export.
1188
 */
1189
function ctools_export_to_hook_code(&$code, $table, $names = array(), $name = 'foo') {
1190
  $schema = ctools_export_get_schema($table);
1191
  $export = $schema['export'];
1192
  // Use the schema-specified function for generating hook code, if one exists.
1193
  if (function_exists($export['to hook code callback'])) {
1194
    $output = $export['to hook code callback']($names, $name);
1195
  }
1196
  // Otherwise, the following code generates basic hook code.
1197
  else {
1198
    $output = ctools_export_default_to_hook_code($schema, $table, $names, $name);
1199
  }
1200

    
1201
  if (!empty($output)) {
1202
    if (isset($export['api'])) {
1203
      if (isset($code[$export['api']['owner']][$export['api']['api']]['version'])) {
1204
        $code[$export['api']['owner']][$export['api']['api']]['version'] = max($code[$export['api']['owner']][$export['api']['api']]['version'], $export['api']['minimum_version']);
1205
      }
1206
      else {
1207
        $code[$export['api']['owner']][$export['api']['api']]['version'] = $export['api']['minimum_version'];
1208
        $code[$export['api']['owner']][$export['api']['api']]['code'] = '';
1209
      }
1210
      $code[$export['api']['owner']][$export['api']['api']]['code'] .= $output;
1211
    }
1212
    else {
1213
      if (empty($code['general'])) {
1214
        $code['general'] = '';
1215
      }
1216
      $code['general'] .= $output;
1217
    }
1218
  }
1219
}
1220

    
1221
/**
1222
 * Default function to export objects to code.
1223
 *
1224
 * Note that if your module provides a 'to hook code callback' then it will
1225
 * receive only $names and $name as arguments. Your module is presumed to
1226
 * already know the rest.
1227
 */
1228
function ctools_export_default_to_hook_code($schema, $table, $names, $name) {
1229
  $export = $schema['export'];
1230
  $output = '';
1231
  $objects = ctools_export_crud_load_multiple($table, $names);
1232
  if ($objects) {
1233
    $output = "/**\n";
1234
    $output .= " * Implements hook_{$export['default hook']}().\n";
1235
    $output .= " */\n";
1236
    $output .= "function " . $name . "_{$export['default hook']}() {\n";
1237
    $output .= "  \${$export['identifier']}s = array();\n\n";
1238
    foreach ($objects as $object) {
1239
      $output .= ctools_export_crud_export($table, $object, '  ');
1240
      $output .= "  \${$export['identifier']}s['" . check_plain($object->{$export['key']}) . "'] = \${$export['identifier']};\n\n";
1241
    }
1242
    $output .= "  return \${$export['identifier']}s;\n";
1243
    $output .= "}\n";
1244
  }
1245

    
1246
  return $output;
1247
}
1248

    
1249
/**
1250
 * Default function for listing bulk exportable objects.
1251
 */
1252
function ctools_export_default_list($table, $schema) {
1253
  $list = array();
1254

    
1255
  $items = ctools_export_crud_load_all($table);
1256
  $export_key = $schema['export']['key'];
1257
  foreach ($items as $item) {
1258
    // Try a couple of possible obvious title keys:
1259
    $keys = array('admin_title', 'title');
1260
    if (isset($schema['export']['admin_title'])) {
1261
      array_unshift($keys, $schema['export']['admin_title']);
1262
    }
1263

    
1264
    $string = '';
1265
    foreach ($keys as $key) {
1266
      if (!empty($item->$key)) {
1267
        $string = $item->$key . " (" . $item->$export_key . ")";
1268
        break;
1269
      }
1270
    }
1271

    
1272
    if (empty($string)) {
1273
      $string = $item->$export_key;
1274
    }
1275
    $list[$item->$export_key] = check_plain($string);
1276
  }
1277
  return $list;
1278
}