Projet

Général

Profil

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

root / drupal7 / sites / all / modules / ctools / includes / export.inc @ 136a805a

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
 * @return
130
 *   An array of all loaded objects, keyed by the unique IDs of the export key.
131
 */
132
function ctools_export_crud_load_all($table, $reset = FALSE) {
133
  $schema = ctools_export_get_schema($table);
134
  if (empty($schema['export'])) {
135
    return array();
136
  }
137

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
297
    return $item;
298
  }
299
}
300

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

    
317
  if (!empty($export['status callback']) && function_exists($export['status callback'])) {
318
    $export['status callback']($object, $status);
319
  }
320
  else {
321
    if (is_object($object)) {
322
      ctools_export_set_object_status($object, $status);
323
    }
324
    else {
325
      ctools_export_set_status($table, $object, $status);
326
    }
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
  else if ($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
          else if ($object->$key != $value) {
513
            continue 2;
514
          }
515
        }
516
      }
517
      else if ($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
  // For conditions,
565
  return $return;
566
}
567

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

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

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

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

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

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

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

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

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

    
637
  return $object;
638
}
639

    
640
/**
641
 * Call the hook to get all default objects of the given type from the
642
 * export. If configured properly, this could include loading up an API
643
 * to get default objects.
644
 */
645
function _ctools_export_get_defaults($table, $export) {
646
  $cache = &drupal_static(__FUNCTION__, array());
647

    
648
  // If defaults may be cached, first see if we can load from cache.
649
  if (!isset($cache[$table]) && !empty($export['cache defaults'])) {
650
    $cache[$table] = _ctools_export_get_defaults_from_cache($table, $export);
651
  }
652

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

    
664
    $cache[$table] = array();
665

    
666
    if ($export['default hook']) {
667
      if (!empty($export['api'])) {
668
        ctools_include('plugins');
669
        $info = ctools_plugin_api_include($export['api']['owner'], $export['api']['api'],
670
          $export['api']['minimum_version'], $export['api']['current_version']);
671
        $modules = array_keys($info);
672
      }
673
      else {
674
        $modules = module_implements($export['default hook']);
675
      }
676

    
677
      foreach ($modules as $module) {
678
        $function = $module . '_' . $export['default hook'];
679
        if (function_exists($function)) {
680
          foreach ((array) $function($export) as $name => $object) {
681
            // Record the module that provides this exportable.
682
            $object->export_module = $module;
683

    
684
            if (empty($export['api'])) {
685
              $cache[$table][$name] = $object;
686
            }
687
            else {
688
              // If version checking is enabled, ensure that the object can be used.
689
              if (isset($object->api_version) &&
690
                version_compare($object->api_version, $export['api']['minimum_version']) >= 0 &&
691
                version_compare($object->api_version, $export['api']['current_version']) <= 0) {
692
                $cache[$table][$name] = $object;
693
              }
694
            }
695
          }
696
        }
697
      }
698

    
699
      drupal_alter($export['default hook'], $cache[$table]);
700

    
701
      // If we acquired a lock earlier, cache the results and release the
702
      // lock.
703
      if (!empty($lock)) {
704
        // Cache the index.
705
        $index = array_keys($cache[$table]);
706
        cache_set('ctools_export_index:' . $table, $index, $export['default cache bin']);
707

    
708
        // Cache each object.
709
        foreach ($cache[$table] as $name => $object) {
710
          cache_set('ctools_export:' . $table . ':' . $name, $object, $export['default cache bin']);
711
        }
712
        lock_release('ctools_export:' . $table);
713
      }
714
    }
715
  }
716

    
717
  return $cache[$table];
718
}
719

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

    
736
  // This is the perfectly valid case where there are no default objects,
737
  // and we have cached this state.
738
  if (empty($data->data)) {
739
    return array();
740
  }
741

    
742
  $keys = array();
743
  foreach ($data->data as $name) {
744
    $keys[] = 'ctools_export:' . $table . ':' . $name;
745
  }
746

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

    
749
  // If any of our indexed keys missed, then we have a fail and we need to
750
  // rebuild.
751
  if (!empty($keys)) {
752
    return;
753
  }
754

    
755
  // Now, translate the returned cache objects to actual objects.
756
  $cache = array();
757
  foreach ($data as $cached_object) {
758
    $cache[$cached_object->data->{$export['key']}] = $cached_object->data;
759
  }
760

    
761
  return $cache;
762
}
763

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

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

    
781
  // Cache hits remove the $key from $keys by reference. Cache
782
  // misses do not. A cache miss indicates we may have to rebuild.
783
  if (!empty($keys)) {
784
    return _ctools_export_get_defaults($table, $export);
785
  }
786

    
787
  // Now, translate the returned cache objects to actual objects.
788
  $cache = array();
789
  foreach ($data as $cached_object) {
790
    $cache[$cached_object->data->{$export['key']}] = $cached_object->data;
791
  }
792

    
793
  return $cache;
794
}
795

    
796
/**
797
 * Unpack data loaded from the database onto an object.
798
 *
799
 * @param $schema
800
 *   The schema from drupal_get_schema().
801
 * @param $data
802
 *   The data as loaded from the database.
803
 * @param $object
804
 *   If an object, data will be unpacked onto it. If a string
805
 *   an object of that type will be created.
806
 */
807
function _ctools_export_unpack_object($schema, $data, $object = 'stdClass') {
808
  if (is_string($object)) {
809
    if (class_exists($object)) {
810
      $object = new $object;
811
    }
812
    else {
813
      $object = new stdClass;
814
    }
815
  }
816

    
817
  // Go through our schema and build correlations.
818
  foreach ($schema['fields'] as $field => $info) {
819
    if (isset($data->$field)) {
820
      $object->$field = empty($info['serialize']) ? $data->$field : unserialize($data->$field);
821
    }
822
    else {
823
      $object->$field = NULL;
824
    }
825
  }
826

    
827
  if (isset($schema['join'])) {
828
    foreach ($schema['join'] as $join_key => $join) {
829
      $join_schema = ctools_export_get_schema($join['table']);
830
      if (!empty($join['load'])) {
831
        foreach ($join['load'] as $field) {
832
          $info = $join_schema['fields'][$field];
833
          $object->$field = empty($info['serialize']) ? $data->$field : unserialize($data->$field);
834
        }
835
      }
836
    }
837
  }
838

    
839
  return $object;
840
}
841

    
842
/**
843
 * Unpack data loaded from the database onto an object.
844
 *
845
 * @param $table
846
 *   The name of the table this object represents.
847
 * @param $data
848
 *   The data as loaded from the database.
849
 */
850
function ctools_export_unpack_object($table, $data) {
851
  $schema = ctools_export_get_schema($table);
852
  return _ctools_export_unpack_object($schema, $data, $schema['export']['object']);
853
}
854

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

    
890
  return $output;
891
}
892

    
893
/**
894
 * Export an object into code.
895
 */
896
function ctools_export_object($table, $object, $indent = '', $identifier = NULL, $additions = array(), $additions2 = array()) {
897
  $schema = ctools_export_get_schema($table);
898
  if (!isset($identifier)) {
899
    $identifier = $schema['export']['identifier'];
900
  }
901

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

    
904
  if ($schema['export']['can disable']) {
905
    $output .= $indent . '$' . $identifier . '->disabled = FALSE; /* Edit this to true to make a default ' . $identifier . ' disabled initially */' . "\n";
906
  }
907
  if (!empty($schema['export']['api']['current_version'])) {
908
    $output .= $indent . '$' . $identifier . '->api_version = ' . $schema['export']['api']['current_version'] . ";\n";
909
  }
910

    
911
  // Put top additions here:
912
  foreach ($additions as $field => $value) {
913
    $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
914
  }
915

    
916
  $fields = $schema['fields'];
917
  if (!empty($schema['join'])) {
918
    foreach ($schema['join'] as $join) {
919
      if (!empty($join['load'])) {
920
        foreach ($join['load'] as $join_field) {
921
          $fields[$join_field] = $join['fields'][$join_field];
922
        }
923
      }
924
    }
925
  }
926

    
927
  // Go through our schema and joined tables and build correlations.
928
  foreach ($fields as $field => $info) {
929
    if (!empty($info['no export'])) {
930
      continue;
931
    }
932
    if (!isset($object->$field)) {
933
      if (isset($info['default'])) {
934
        $object->$field = $info['default'];
935
      }
936
      else {
937
        $object->$field = '';
938
      }
939
    }
940

    
941
    // Note: This is the *field* export callback, not the table one!
942
    if (!empty($info['export callback']) && function_exists($info['export callback'])) {
943
      $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . $info['export callback']($object, $field, $object->$field, $indent) . ";\n";
944
    }
945
    else {
946
      $value = $object->$field;
947
      if ($info['type'] == 'int') {
948
        if (isset($info['size']) && $info['size'] == 'tiny') {
949
          $info['boolean'] = (!isset($info['boolean'])) ? $schema['export']['boolean'] : $info['boolean'];
950
          $value = ($info['boolean']) ? (bool) $value : (int) $value;
951
        }
952
        else {
953
          $value = (int) $value;
954
        }
955
      }
956

    
957
      $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
958
    }
959
  }
960

    
961
  // And bottom additions here
962
  foreach ($additions2 as $field => $value) {
963
    $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
964
  }
965

    
966
  return $output;
967
}
968

    
969
/**
970
 * Get the schema for a given table.
971
 *
972
 * This looks for data the export subsystem needs and applies defaults so
973
 * that it's easily available.
974
 */
975
function ctools_export_get_schema($table) {
976
  static $drupal_static_fast;
977
  if (!isset($drupal_static_fast)) {
978
    $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
979
  }
980
  $cache = &$drupal_static_fast['cache'];
981

    
982
  if (empty($cache[$table])) {
983
    $schema = drupal_get_schema($table);
984

    
985
    // If our schema isn't loaded, it's possible we're in a state where it
986
    // simply hasn't been cached. If we've been asked, let's force the
987
    // issue.
988
    if (!$schema || empty($schema['export'])) {
989
      // force a schema reset:
990
      $schema = drupal_get_schema($table, TRUE);
991
    }
992

    
993
    if (!isset($schema['export'])) {
994
      return array();
995
    }
996

    
997
    if (empty($schema['module'])) {
998
      return array();
999
    }
1000

    
1001
    // Add some defaults
1002
    $schema['export'] += array(
1003
      'key' => 'name',
1004
      'key name' => 'Name',
1005
      'object' => 'stdClass',
1006
      'status' => 'default_' . $table,
1007
      'default hook' => 'default_' . $table,
1008
      'can disable' => TRUE,
1009
      'identifier' => $table,
1010
      'primary key' => !empty($schema['primary key']) ? $schema['primary key'][0] : '',
1011
      'bulk export' => TRUE,
1012
      'list callback' => "$schema[module]_{$table}_list",
1013
      'to hook code callback' => "$schema[module]_{$table}_to_hook_code",
1014
      'cache defaults' => FALSE,
1015
      'default cache bin' => 'cache',
1016
      'export type string' => 'type',
1017
      'boolean' => TRUE,
1018
    );
1019

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

    
1026
    // Notes:
1027
    // The following callbacks may be defined to override default behavior
1028
    // when using CRUD functions:
1029
    //
1030
    // create callback
1031
    // load callback
1032
    // load multiple callback
1033
    // load all callback
1034
    // save callback
1035
    // delete callback
1036
    // export callback
1037
    // import callback
1038
    //
1039
    // See the appropriate ctools_export_crud function for details on what
1040
    // arguments these callbacks should accept. Please do not call these
1041
    // directly, always use the ctools_export_crud_* wrappers to ensure
1042
    // that default implementations are honored.
1043
    $cache[$table] = $schema;
1044
  }
1045

    
1046
  return $cache[$table];
1047
}
1048

    
1049
/**
1050
 * Gets the schemas for all tables with ctools object metadata.
1051
 */
1052
function ctools_export_get_schemas($for_export = FALSE) {
1053
  $export_tables = &drupal_static(__FUNCTION__);
1054
  if (is_null($export_tables)) {
1055
    $export_tables = array();
1056
    $schemas = drupal_get_schema();
1057
    foreach ($schemas as $table => $schema) {
1058
      if (!isset($schema['export'])) {
1059
        unset($schemas[$table]);
1060
        continue;
1061
      }
1062
      $export_tables[$table] = ctools_export_get_schema($table);
1063
    }
1064
  }
1065
  return $for_export ? array_filter($export_tables, '_ctools_export_filter_export_tables') : $export_tables;
1066
}
1067

    
1068
function _ctools_export_filter_export_tables($schema) {
1069
  return !empty($schema['export']['bulk export']);
1070
}
1071

    
1072
function ctools_export_get_schemas_by_module($modules = array(), $for_export = FALSE) {
1073
  $export_tables = array();
1074
  $list = ctools_export_get_schemas($for_export);
1075
  foreach ($list as $table => $schema) {
1076
    $export_tables[$schema['module']][$table] = $schema;
1077
  }
1078
  return empty($modules) ? $export_tables : array_keys($export_tables, $modules);
1079
}
1080

    
1081
/**
1082
 * Set the status of a default $object as a variable.
1083
 *
1084
 * The status, in this case, is whether or not it is 'disabled'.
1085
 * This function does not check to make sure $object actually
1086
 * exists.
1087
 */
1088
function ctools_export_set_status($table, $name, $new_status = TRUE) {
1089
  $schema = ctools_export_get_schema($table);
1090
  $status = variable_get($schema['export']['status'], array());
1091

    
1092
  $status[$name] = $new_status;
1093
  variable_set($schema['export']['status'], $status);
1094
}
1095

    
1096
/**
1097
 * Set the status of a default $object as a variable.
1098
 *
1099
 * This is more efficient than ctools_export_set_status because it
1100
 * will actually unset the variable entirely if it's not necessary,
1101
 * this saving a bit of space.
1102
 */
1103
function ctools_export_set_object_status($object, $new_status = TRUE) {
1104
  $table = $object->table;
1105
  $schema = ctools_export_get_schema($table);
1106
  $export = $schema['export'];
1107
  $status = variable_get($export['status'], array());
1108

    
1109
  // Compare
1110
  if (!$new_status && $object->export_type & EXPORT_IN_DATABASE) {
1111
    unset($status[$object->{$export['key']}]);
1112
  }
1113
  else {
1114
    $status[$object->{$export['key']}] = $new_status;
1115
  }
1116

    
1117
  variable_set($export['status'], $status);
1118
}
1119

    
1120
/**
1121
 * Provide a form for displaying an export.
1122
 *
1123
 * This is a simple form that should be invoked like this:
1124
 * @code
1125
 *   $output = drupal_get_form('ctools_export_form', $code, $object_title);
1126
 * @endcode
1127
 */
1128
function ctools_export_form($form, &$form_state, $code, $title = '') {
1129
  $lines = substr_count($code, "\n");
1130
  $form['code'] = array(
1131
    '#type' => 'textarea',
1132
    '#title' => $title,
1133
    '#default_value' => $code,
1134
    '#rows' => $lines,
1135
  );
1136

    
1137
  return $form;
1138
}
1139

    
1140
/**
1141
 * Create a new object based upon schema values.
1142
 *
1143
 * Because 'default' has ambiguous meaning on some fields, we will actually
1144
 * use 'object default' to fill in default values if default is not set
1145
 * That's a little safer to use as it won't cause weird database default
1146
 * situations.
1147
 */
1148
function ctools_export_new_object($table, $set_defaults = TRUE) {
1149
  $schema = ctools_export_get_schema($table);
1150
  $export = $schema['export'];
1151

    
1152
  $object = new $export['object'];
1153
  foreach ($schema['fields'] as $field => $info) {
1154
    if (isset($info['object default'])) {
1155
      $object->$field = $info['object default'];
1156
    }
1157
    else if (isset($info['default'])) {
1158
      $object->$field = $info['default'];
1159
    }
1160
    else {
1161
      $object->$field = NULL;
1162
    }
1163
  }
1164

    
1165
  if ($set_defaults) {
1166
    // Set some defaults so this data always exists.
1167
    // We don't set the export_type property here, as this object is not saved
1168
    // yet. We do give it NULL so we don't generate notices trying to read it.
1169
    $object->export_type = NULL;
1170
    $object->{$export['export type string']} = t('Local');
1171
  }
1172
  return $object;
1173
}
1174

    
1175
/**
1176
 * Convert a group of objects to code based upon input and return this as a larger
1177
 * export.
1178
 */
1179
function ctools_export_to_hook_code(&$code, $table, $names = array(), $name = 'foo') {
1180
  $schema = ctools_export_get_schema($table);
1181
  $export = $schema['export'];
1182
  // Use the schema-specified function for generating hook code, if one exists
1183
  if (function_exists($export['to hook code callback'])) {
1184
    $output = $export['to hook code callback']($names, $name);
1185
  }
1186
  // Otherwise, the following code generates basic hook code
1187
  else {
1188
    $output = ctools_export_default_to_hook_code($schema, $table, $names, $name);
1189
  }
1190

    
1191
  if (!empty($output)) {
1192
    if (isset($export['api'])) {
1193
      if (isset($code[$export['api']['owner']][$export['api']['api']]['version'])) {
1194
        $code[$export['api']['owner']][$export['api']['api']]['version'] = max($code[$export['api']['owner']][$export['api']['api']]['version'], $export['api']['minimum_version']);
1195
      }
1196
      else {
1197
        $code[$export['api']['owner']][$export['api']['api']]['version'] = $export['api']['minimum_version'];
1198
        $code[$export['api']['owner']][$export['api']['api']]['code'] = '';
1199
      }
1200
      $code[$export['api']['owner']][$export['api']['api']]['code'] .= $output;
1201
    }
1202
    else {
1203
      if (empty($code['general'])) {
1204
        $code['general'] = '';
1205
      }
1206
      $code['general'] .= $output;
1207
    }
1208
  }
1209
}
1210

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

    
1236
  return $output;
1237
}
1238
/**
1239
 * Default function for listing bulk exportable objects.
1240
 */
1241
function ctools_export_default_list($table, $schema) {
1242
  $list = array();
1243

    
1244
  $items = ctools_export_crud_load_all($table);
1245
  $export_key = $schema['export']['key'];
1246
  foreach ($items as $item) {
1247
    // Try a couple of possible obvious title keys:
1248
    $keys = array('admin_title', 'title');
1249
    if (isset($schema['export']['admin_title'])) {
1250
      array_unshift($keys, $schema['export']['admin_title']);
1251
    }
1252

    
1253
    $string = '';
1254
    foreach ($keys as $key) {
1255
      if (!empty($item->$key)) {
1256
        $string = $item->$key . " (" . $item->$export_key . ")";
1257
        break;
1258
      }
1259
    }
1260

    
1261
    if (empty($string)) {
1262
      $string = $item->$export_key;
1263
    }
1264
    $list[$item->$export_key] = check_plain($string);
1265
  }
1266
  return $list;
1267
}