Projet

Général

Profil

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

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

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
    $disabled = !isset($object->disabled) || $object->disabled != TRUE ? 'FALSE' : 'TRUE';
906
    $output .= $indent . '$' . $identifier . '->disabled = ' . $disabled . '; /* Edit this to true to make a default ' . $identifier . ' disabled initially */' . "\n";
907
  }
908
  if (!empty($schema['export']['api']['current_version'])) {
909
    $output .= $indent . '$' . $identifier . '->api_version = ' . $schema['export']['api']['current_version'] . ";\n";
910
  }
911

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

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

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

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

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

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

    
967
  return $output;
968
}
969

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1138
  return $form;
1139
}
1140

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

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

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

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

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

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

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

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

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

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