Mercurial > hg > rr-repo
comparison sites/all/modules/ctools/includes/export.inc @ 0:ff03f76ab3fe
initial version
author | danieleb <danielebarchiesi@me.com> |
---|---|
date | Wed, 21 Aug 2013 18:51:11 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:ff03f76ab3fe |
---|---|
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 $value = (isset($info['size']) && $info['size'] == 'tiny') ? (bool) $value : (int) $value; | |
949 } | |
950 | |
951 $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n"; | |
952 } | |
953 } | |
954 | |
955 // And bottom additions here | |
956 foreach ($additions2 as $field => $value) { | |
957 $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n"; | |
958 } | |
959 | |
960 return $output; | |
961 } | |
962 | |
963 /** | |
964 * Get the schema for a given table. | |
965 * | |
966 * This looks for data the export subsystem needs and applies defaults so | |
967 * that it's easily available. | |
968 */ | |
969 function ctools_export_get_schema($table) { | |
970 $cache = &drupal_static(__FUNCTION__); | |
971 if (empty($cache[$table])) { | |
972 $schema = drupal_get_schema($table); | |
973 | |
974 // If our schema isn't loaded, it's possible we're in a state where it | |
975 // simply hasn't been cached. If we've been asked, let's force the | |
976 // issue. | |
977 if (!$schema || empty($schema['export'])) { | |
978 // force a schema reset: | |
979 $schema = drupal_get_schema($table, TRUE); | |
980 } | |
981 | |
982 if (!isset($schema['export'])) { | |
983 return array(); | |
984 } | |
985 | |
986 if (empty($schema['module'])) { | |
987 return array(); | |
988 } | |
989 | |
990 // Add some defaults | |
991 $schema['export'] += array( | |
992 'key' => 'name', | |
993 'key name' => 'Name', | |
994 'object' => 'stdClass', | |
995 'status' => 'default_' . $table, | |
996 'default hook' => 'default_' . $table, | |
997 'can disable' => TRUE, | |
998 'identifier' => $table, | |
999 'primary key' => !empty($schema['primary key']) ? $schema['primary key'][0] : '', | |
1000 'bulk export' => TRUE, | |
1001 'list callback' => "$schema[module]_{$table}_list", | |
1002 'to hook code callback' => "$schema[module]_{$table}_to_hook_code", | |
1003 'cache defaults' => FALSE, | |
1004 'default cache bin' => 'cache', | |
1005 'export type string' => 'type', | |
1006 ); | |
1007 | |
1008 // If the export definition doesn't have the "primary key" then the CRUD | |
1009 // save callback won't work. | |
1010 if (empty($schema['export']['primary key']) && user_access('administer site configuration')) { | |
1011 drupal_set_message(t('The export definition of @table is missing the "primary key" property.', array('@table' => $table)), 'error'); | |
1012 } | |
1013 | |
1014 // Notes: | |
1015 // The following callbacks may be defined to override default behavior | |
1016 // when using CRUD functions: | |
1017 // | |
1018 // create callback | |
1019 // load callback | |
1020 // load multiple callback | |
1021 // load all callback | |
1022 // save callback | |
1023 // delete callback | |
1024 // export callback | |
1025 // import callback | |
1026 // | |
1027 // See the appropriate ctools_export_crud function for details on what | |
1028 // arguments these callbacks should accept. Please do not call these | |
1029 // directly, always use the ctools_export_crud_* wrappers to ensure | |
1030 // that default implementations are honored. | |
1031 $cache[$table] = $schema; | |
1032 } | |
1033 | |
1034 return $cache[$table]; | |
1035 } | |
1036 | |
1037 /** | |
1038 * Gets the schemas for all tables with ctools object metadata. | |
1039 */ | |
1040 function ctools_export_get_schemas($for_export = FALSE) { | |
1041 $export_tables = &drupal_static(__FUNCTION__); | |
1042 if (is_null($export_tables)) { | |
1043 $export_tables = array(); | |
1044 $schemas = drupal_get_schema(); | |
1045 foreach ($schemas as $table => $schema) { | |
1046 if (!isset($schema['export'])) { | |
1047 unset($schemas[$table]); | |
1048 continue; | |
1049 } | |
1050 $export_tables[$table] = ctools_export_get_schema($table); | |
1051 } | |
1052 } | |
1053 return $for_export ? array_filter($export_tables, '_ctools_export_filter_export_tables') : $export_tables; | |
1054 } | |
1055 | |
1056 function _ctools_export_filter_export_tables($schema) { | |
1057 return !empty($schema['export']['bulk export']); | |
1058 } | |
1059 | |
1060 function ctools_export_get_schemas_by_module($modules = array(), $for_export = FALSE) { | |
1061 $export_tables = array(); | |
1062 $list = ctools_export_get_schemas($for_export); | |
1063 foreach ($list as $table => $schema) { | |
1064 $export_tables[$schema['module']][$table] = $schema; | |
1065 } | |
1066 return empty($modules) ? $export_tables : array_keys($export_tables, $modules); | |
1067 } | |
1068 | |
1069 /** | |
1070 * Set the status of a default $object as a variable. | |
1071 * | |
1072 * The status, in this case, is whether or not it is 'disabled'. | |
1073 * This function does not check to make sure $object actually | |
1074 * exists. | |
1075 */ | |
1076 function ctools_export_set_status($table, $name, $new_status = TRUE) { | |
1077 $schema = ctools_export_get_schema($table); | |
1078 $status = variable_get($schema['export']['status'], array()); | |
1079 | |
1080 $status[$name] = $new_status; | |
1081 variable_set($schema['export']['status'], $status); | |
1082 } | |
1083 | |
1084 /** | |
1085 * Set the status of a default $object as a variable. | |
1086 * | |
1087 * This is more efficient than ctools_export_set_status because it | |
1088 * will actually unset the variable entirely if it's not necessary, | |
1089 * this saving a bit of space. | |
1090 */ | |
1091 function ctools_export_set_object_status($object, $new_status = TRUE) { | |
1092 $table = $object->table; | |
1093 $schema = ctools_export_get_schema($table); | |
1094 $export = $schema['export']; | |
1095 $status = variable_get($export['status'], array()); | |
1096 | |
1097 // Compare | |
1098 if (!$new_status && $object->export_type & EXPORT_IN_DATABASE) { | |
1099 unset($status[$object->{$export['key']}]); | |
1100 } | |
1101 else { | |
1102 $status[$object->{$export['key']}] = $new_status; | |
1103 } | |
1104 | |
1105 variable_set($export['status'], $status); | |
1106 } | |
1107 | |
1108 /** | |
1109 * Provide a form for displaying an export. | |
1110 * | |
1111 * This is a simple form that should be invoked like this: | |
1112 * @code | |
1113 * $output = drupal_get_form('ctools_export_form', $code, $object_title); | |
1114 * @endcode | |
1115 */ | |
1116 function ctools_export_form($form, &$form_state, $code, $title = '') { | |
1117 $lines = substr_count($code, "\n"); | |
1118 $form['code'] = array( | |
1119 '#type' => 'textarea', | |
1120 '#title' => $title, | |
1121 '#default_value' => $code, | |
1122 '#rows' => $lines, | |
1123 ); | |
1124 | |
1125 return $form; | |
1126 } | |
1127 | |
1128 /** | |
1129 * Create a new object based upon schema values. | |
1130 * | |
1131 * Because 'default' has ambiguous meaning on some fields, we will actually | |
1132 * use 'object default' to fill in default values if default is not set | |
1133 * That's a little safer to use as it won't cause weird database default | |
1134 * situations. | |
1135 */ | |
1136 function ctools_export_new_object($table, $set_defaults = TRUE) { | |
1137 $schema = ctools_export_get_schema($table); | |
1138 $export = $schema['export']; | |
1139 | |
1140 $object = new $export['object']; | |
1141 foreach ($schema['fields'] as $field => $info) { | |
1142 if (isset($info['object default'])) { | |
1143 $object->$field = $info['object default']; | |
1144 } | |
1145 else if (isset($info['default'])) { | |
1146 $object->$field = $info['default']; | |
1147 } | |
1148 else { | |
1149 $object->$field = NULL; | |
1150 } | |
1151 } | |
1152 | |
1153 if ($set_defaults) { | |
1154 // Set some defaults so this data always exists. | |
1155 // We don't set the export_type property here, as this object is not saved | |
1156 // yet. We do give it NULL so we don't generate notices trying to read it. | |
1157 $object->export_type = NULL; | |
1158 $object->{$export['export type string']} = t('Local'); | |
1159 } | |
1160 return $object; | |
1161 } | |
1162 | |
1163 /** | |
1164 * Convert a group of objects to code based upon input and return this as a larger | |
1165 * export. | |
1166 */ | |
1167 function ctools_export_to_hook_code(&$code, $table, $names = array(), $name = 'foo') { | |
1168 $schema = ctools_export_get_schema($table); | |
1169 $export = $schema['export']; | |
1170 // Use the schema-specified function for generating hook code, if one exists | |
1171 if (function_exists($export['to hook code callback'])) { | |
1172 $output = $export['to hook code callback']($names, $name); | |
1173 } | |
1174 // Otherwise, the following code generates basic hook code | |
1175 else { | |
1176 $output = ctools_export_default_to_hook_code($schema, $table, $names, $name); | |
1177 } | |
1178 | |
1179 if (!empty($output)) { | |
1180 if (isset($export['api'])) { | |
1181 if (isset($code[$export['api']['owner']][$export['api']['api']]['version'])) { | |
1182 $code[$export['api']['owner']][$export['api']['api']]['version'] = max($code[$export['api']['owner']][$export['api']['api']]['version'], $export['api']['minimum_version']); | |
1183 } | |
1184 else { | |
1185 $code[$export['api']['owner']][$export['api']['api']]['version'] = $export['api']['minimum_version']; | |
1186 $code[$export['api']['owner']][$export['api']['api']]['code'] = ''; | |
1187 } | |
1188 $code[$export['api']['owner']][$export['api']['api']]['code'] .= $output; | |
1189 } | |
1190 else { | |
1191 if (empty($code['general'])) { | |
1192 $code['general'] = ''; | |
1193 } | |
1194 $code['general'] .= $output; | |
1195 } | |
1196 } | |
1197 } | |
1198 | |
1199 /** | |
1200 * Default function to export objects to code. | |
1201 * | |
1202 * Note that if your module provides a 'to hook code callback' then it will | |
1203 * receive only $names and $name as arguments. Your module is presumed to | |
1204 * already know the rest. | |
1205 */ | |
1206 function ctools_export_default_to_hook_code($schema, $table, $names, $name) { | |
1207 $export = $schema['export']; | |
1208 $output = ''; | |
1209 $objects = ctools_export_crud_load_multiple($table, $names); | |
1210 if ($objects) { | |
1211 $output = "/**\n"; | |
1212 $output .= " * Implements hook_{$export['default hook']}().\n"; | |
1213 $output .= " */\n"; | |
1214 $output .= "function " . $name . "_{$export['default hook']}() {\n"; | |
1215 $output .= " \${$export['identifier']}s = array();\n\n"; | |
1216 foreach ($objects as $object) { | |
1217 $output .= ctools_export_crud_export($table, $object, ' '); | |
1218 $output .= " \${$export['identifier']}s['" . check_plain($object->$export['key']) . "'] = \${$export['identifier']};\n\n"; | |
1219 } | |
1220 $output .= " return \${$export['identifier']}s;\n"; | |
1221 $output .= "}\n"; | |
1222 } | |
1223 | |
1224 return $output; | |
1225 } | |
1226 /** | |
1227 * Default function for listing bulk exportable objects. | |
1228 */ | |
1229 function ctools_export_default_list($table, $schema) { | |
1230 $list = array(); | |
1231 | |
1232 $items = ctools_export_crud_load_all($table); | |
1233 $export_key = $schema['export']['key']; | |
1234 foreach ($items as $item) { | |
1235 // Try a couple of possible obvious title keys: | |
1236 $keys = array('admin_title', 'title'); | |
1237 if (isset($schema['export']['admin_title'])) { | |
1238 array_unshift($keys, $schema['export']['admin_title']); | |
1239 } | |
1240 | |
1241 $string = ''; | |
1242 foreach ($keys as $key) { | |
1243 if (!empty($item->$key)) { | |
1244 $string = $item->$key . " (" . $item->$export_key . ")"; | |
1245 break; | |
1246 } | |
1247 } | |
1248 | |
1249 if (empty($string)) { | |
1250 $string = $item->$export_key; | |
1251 } | |
1252 $list[$item->$export_key] = check_plain($string); | |
1253 } | |
1254 return $list; | |
1255 } |