rules.state.inc
27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
<?php
/**
* @file Contains the state and data related stuff.
*/
/**
* The rules evaluation state.
*
* A rule element may clone the state, so any added variables are only visible
* for elements in the current PHP-variable-scope.
*/
class RulesState {
/**
* Globally keeps the ids of rules blocked due to recursion prevention.
*/
static protected $blocked = array();
/**
* The known variables.
*/
public $variables = array();
/**
* Holds info about the variables.
*/
protected $info = array();
/**
* Keeps wrappers to be saved later on.
*/
protected $save;
/**
* Holds the arguments while an element is executed. May be used by the
* element to easily access the wrapped arguments.
*/
public $currentArguments;
/**
* Variable for saving currently blocked configs for serialization.
*/
protected $currentlyBlocked;
public function __construct() {
// Use an object in order to ensure any cloned states reference the same
// save information.
$this->save = new ArrayObject();
$this->addVariable('site', FALSE, self::defaultVariables('site'));
}
/**
* Adds the given variable to the given execution state.
*/
public function addVariable($name, $data, $info) {
$this->info[$name] = $info + array(
'skip save' => FALSE,
'type' => 'unknown',
'handler' => FALSE,
);
if (empty($this->info[$name]['handler'])) {
$this->variables[$name] = rules_wrap_data($data, $this->info[$name]);
}
}
/**
* Runs post-evaluation tasks, such as saving variables.
*/
public function cleanUp() {
// Make changes permanent.
foreach ($this->save->getArrayCopy() as $selector => $wrapper) {
$this->saveNow($selector);
}
unset($this->currentArguments);
}
/**
* Block a rules configuration from execution.
*/
public function block($rules_config) {
if (empty($rules_config->recursion) && $rules_config->id) {
self::$blocked[$rules_config->id] = TRUE;
}
}
/**
* Unblock a rules configuration from execution.
*/
public function unblock($rules_config) {
if (empty($rules_config->recursion) && $rules_config->id) {
unset(self::$blocked[$rules_config->id]);
}
}
/**
* Returns whether a rules configuration should be blocked from execution.
*/
public function isBlocked($rule_config) {
return !empty($rule_config->id) && isset(self::$blocked[$rule_config->id]);
}
/**
* Get the info about the state variables or a single variable.
*/
public function varInfo($name = NULL) {
if (isset($name)) {
return isset($this->info[$name]) ? $this->info[$name] : FALSE;
}
return $this->info;
}
/**
* Returns whether the given wrapper is savable.
*/
public function isSavable($wrapper) {
return ($wrapper instanceof EntityDrupalWrapper && entity_type_supports($wrapper->type(), 'save')) || $wrapper instanceof RulesDataWrapperSavableInterface;
}
/**
* Returns whether the variable with the given name is an entity.
*/
public function isEntity($name) {
$entity_info = entity_get_info();
return isset($this->info[$name]['type']) && isset($entity_info[$this->info[$name]['type']]);
}
/**
* Gets a variable.
*
* If necessary, the specified handler is invoked to fetch the variable.
*
* @param $name
* The name of the variable to return.
*
* @return
* The variable or a EntityMetadataWrapper containing the variable.
*
* @throws RulesEvaluationException
* Throws a RulesEvaluationException in case we have info about the
* requested variable, but it is not defined.
*/
public function &get($name) {
if (!array_key_exists($name, $this->variables)) {
// If there is handler to load the variable, do it now.
if (!empty($this->info[$name]['handler'])) {
$data = call_user_func($this->info[$name]['handler'], rules_unwrap_data($this->variables), $name, $this->info[$name]);
$this->variables[$name] = rules_wrap_data($data, $this->info[$name]);
$this->info[$name]['handler'] = FALSE;
if (!isset($data)) {
throw new RulesEvaluationException('Unable to load variable %name, aborting.', array('%name' => $name), NULL, RulesLog::INFO);
}
}
else {
throw new RulesEvaluationException('Unable to get variable %name, it is not defined.', array('%name' => $name), NULL, RulesLog::ERROR);
}
}
return $this->variables[$name];
}
/**
* Apply permanent changes provided the wrapper's data type is savable.
*
* @param $selector
* The data selector of the wrapper to save or just a variable name.
* @param $immediate
* Pass FALSE to postpone saving to later on. Else it's immediately saved.
*/
public function saveChanges($selector, $wrapper, $immediate = FALSE) {
$info = $wrapper->info();
if (empty($info['skip save']) && $this->isSavable($wrapper)) {
$this->save($selector, $wrapper, $immediate);
}
// No entity, so try saving the parent.
elseif (empty($info['skip save']) && isset($info['parent']) && !($wrapper instanceof EntityDrupalWrapper)) {
// Cut of the last part of the selector.
$selector = implode(':', explode(':', $selector, -1));
$this->saveChanges($selector, $info['parent'], $immediate);
}
return $this;
}
/**
* Remembers to save the wrapper on cleanup or does it now.
*/
protected function save($selector, EntityMetadataWrapper $wrapper, $immediate) {
// Convert variable names and selectors to both use underscores.
$selector = strtr($selector, '-', '_');
if (isset($this->save[$selector])) {
if ($this->save[$selector][0]->getIdentifier() == $wrapper->getIdentifier()) {
// The entity is already remembered. So do a combined save.
$this->save[$selector][1] += self::$blocked;
}
else {
// The wrapper is already in there, but wraps another entity. So first
// save the old one, then care about the new one.
$this->saveNow($selector);
}
}
if (!isset($this->save[$selector])) {
// In case of immediate saving don't clone the wrapper, so saving a new
// entity immediately makes the identifier available afterwards.
$this->save[$selector] = array($immediate ? $wrapper : clone $wrapper, self::$blocked);
}
if ($immediate) {
$this->saveNow($selector);
}
}
/**
* Saves the wrapper for the given selector.
*/
protected function saveNow($selector) {
// Add the set of blocked elements for the recursion prevention.
$previously_blocked = self::$blocked;
self::$blocked += $this->save[$selector][1];
// Actually save!
$wrapper = $this->save[$selector][0];
$entity = $wrapper->value();
// When operating in hook_entity_insert() $entity->is_new might be still
// set. In that case remove the flag to avoid causing another insert instead
// of an update.
if (!empty($entity->is_new) && $wrapper->getIdentifier()) {
$entity->is_new = FALSE;
}
rules_log('Saved %selector of type %type.', array('%selector' => $selector, '%type' => $wrapper->type()));
$wrapper->save();
// Restore the state's set of blocked elements.
self::$blocked = $previously_blocked;
unset($this->save[$selector]);
}
/**
* Merges the info about to be saved variables form the given state into the
* existing state. Therefor we can aggregate saves from invoked components.
* Merged in saves are removed from the given state, but not mergable saves
* remain there.
*
* @param $state
* The state for which to merge the to be saved variables in.
* @param $component
* The component which has been invoked, thus needs to be blocked for the
* merged in saves.
* @param $settings
* The settings of the element that invoked the component. Contains
* information about variable/selector mappings between the states.
*/
public function mergeSaveVariables(RulesState $state, RulesPlugin $component, $settings) {
// For any saves that we take over, also block the component.
$this->block($component);
foreach ($state->save->getArrayCopy() as $selector => $data) {
$parts = explode(':', $selector, 2);
// Adapt the selector to fit for the parent state and move the wrapper.
if (isset($settings[$parts[0] . ':select'])) {
$parts[0] = $settings[$parts[0] . ':select'];
$this->save(implode(':', $parts), $data[0], FALSE);
unset($state->save[$selector]);
}
}
$this->unblock($component);
}
/**
* Returns an entity metadata wrapper as specified in the selector.
*
* @param $selector
* The selector string, e.g. "node:author:mail".
* @param $langcode
* (optional) The language code used to get the argument value if the
* argument value should be translated. Defaults to LANGUAGE_NONE.
*
* @return EntityMetadataWrapper
* The wrapper for the given selector.
*
* @throws RulesEvaluationException
* Throws a RulesEvaluationException in case the selector cannot be applied.
*/
public function applyDataSelector($selector, $langcode = LANGUAGE_NONE) {
$parts = explode(':', str_replace('-', '_', $selector), 2);
$wrapper = $this->get($parts[0]);
if (count($parts) == 1) {
return $wrapper;
}
elseif (!$wrapper instanceof EntityMetadataWrapper) {
throw new RulesEvaluationException('Unable to apply data selector %selector. The specified variable is not wrapped correctly.', array('%selector' => $selector));
}
try {
foreach (explode(':', $parts[1]) as $name) {
if ($wrapper instanceof EntityListWrapper || $wrapper instanceof EntityStructureWrapper) {
// Make sure we are usign the right language. Wrappers might be cached
// and have previous langcodes set, so always set the right language.
if ($wrapper instanceof EntityStructureWrapper) {
$wrapper->language($langcode);
}
$wrapper = $wrapper->get($name);
}
else {
throw new RulesEvaluationException('Unable to apply data selector %selector. The specified variable is not a list or a structure: %wrapper.', array('%selector' => $selector, '%wrapper' => $wrapper));
}
}
}
catch (EntityMetadataWrapperException $e) {
// In case of an exception, re-throw it.
throw new RulesEvaluationException('Unable to apply data selector %selector: %error', array('%selector' => $selector, '%error' => $e->getMessage()));
}
return $wrapper;
}
/**
* Magic method. Only serialize variables and their info.
* Additionally we remember currently blocked configs, so we can restore them
* upon deserialization using restoreBlocks().
*/
public function __sleep () {
$this->currentlyBlocked = self::$blocked;
return array('info', 'variables', 'currentlyBlocked');
}
public function __wakeup() {
$this->save = new ArrayObject();
}
/**
* Restore the before serialization blocked configurations.
*
* Warning: This overwrites any possible currently blocked configs. Thus
* do not invoke this method, if there might be evaluations active.
*/
public function restoreBlocks() {
self::$blocked = $this->currentlyBlocked;
}
/**
* Defines always available variables.
*/
public static function defaultVariables($key = NULL) {
// Add a variable for accessing site-wide data properties.
$vars['site'] = array(
'type' => 'site',
'label' => t('Site information'),
'description' => t("Site-wide settings and other global information."),
// Add the property info via a callback making use of the cached info.
'property info alter' => array('RulesData', 'addSiteMetadata'),
'property info' => array(),
'optional' => TRUE,
);
return isset($key) ? $vars[$key] : $vars;
}
}
/**
* A class holding static methods related to data.
*/
class RulesData {
/**
* Returns whether the type match. They match if type1 is compatible to type2.
*
* @param $var_info
* The name of the type to check for whether it is compatible to type2.
* @param $param_info
* The type expression to check for.
* @param $ancestors
* Whether sub-type relationships for checking type compatibility should be
* taken into account. Defaults to TRUE.
*
* @return
* Whether the types match.
*/
public static function typesMatch($var_info, $param_info, $ancestors = TRUE) {
$var_type = $var_info['type'];
$param_type = $param_info['type'];
if ($param_type == '*' || $param_type == 'unknown') {
return TRUE;
}
if ($var_type == $param_type) {
// Make sure the bundle matches, if specified by the parameter.
return !isset($param_info['bundles']) || isset($var_info['bundle']) && in_array($var_info['bundle'], $param_info['bundles']);
}
// Parameters may specify multiple types using an array.
$valid_types = is_array($param_type) ? $param_type : array($param_type);
if (in_array($var_type, $valid_types)) {
return TRUE;
}
// Check for sub-type relationships.
if ($ancestors && !isset($param_info['bundles'])) {
$cache = &rules_get_cache();
self::typeCalcAncestors($cache, $var_type);
// If one of the types is an ancestor return TRUE.
return (bool)array_intersect_key($cache['data_info'][$var_type]['ancestors'], array_flip($valid_types));
}
return FALSE;
}
protected static function typeCalcAncestors(&$cache, $type) {
if (!isset($cache['data_info'][$type]['ancestors'])) {
$cache['data_info'][$type]['ancestors'] = array();
if (isset($cache['data_info'][$type]['parent']) && $parent = $cache['data_info'][$type]['parent']) {
$cache['data_info'][$type]['ancestors'][$parent] = TRUE;
self::typeCalcAncestors($cache, $parent);
// Add all parent ancestors to our own ancestors.
$cache['data_info'][$type]['ancestors'] += $cache['data_info'][$parent]['ancestors'];
}
// For special lists like list<node> add in "list" as valid parent.
if (entity_property_list_extract_type($type)) {
$cache['data_info'][$type]['ancestors']['list'] = TRUE;
}
}
}
/**
* Returns matching data variables or properties for the given info and the to
* be configured parameter.
*
* @param $source
* Either an array of info about available variables or a entity metadata
* wrapper.
* @param $param_info
* The information array about the to be configured parameter.
* @param $prefix
* An optional prefix for the data selectors.
* @param $recursions
* The number of recursions used to go down the tree. Defaults to 2.
* @param $suggestions
* Whether possibilities to recurse are suggested as soon as the deepest
* level of recursions is reached. Defaults to TRUE.
*
* @return
* An array of info about matching variables or properties that match, keyed
* with the data selector.
*/
public static function matchingDataSelector($source, $param_info, $prefix = '', $recursions = 2, $suggestions = TRUE) {
// If an array of info is given, get entity metadata wrappers first.
$data = NULL;
if (is_array($source)) {
foreach ($source as $name => $info) {
$source[$name] = rules_wrap_data($data, $info, TRUE);
}
}
$matches = array();
foreach ($source as $name => $wrapper) {
$info = $wrapper->info();
$name = str_replace('_', '-', $name);
if (self::typesMatch($info, $param_info)) {
$matches[$prefix . $name] = $info;
if (!is_array($source) && $source instanceof EntityListWrapper) {
// Add some more possible list items.
for ($i = 1; $i < 4; $i++) {
$matches[$prefix . $i] = $info;
}
}
}
// Recurse later on to get an improved ordering of the results.
if ($wrapper instanceof EntityStructureWrapper || $wrapper instanceof EntityListWrapper) {
$recurse[$prefix . $name] = $wrapper;
if ($recursions > 0) {
$matches += self::matchingDataSelector($wrapper, $param_info, $prefix . $name . ':', $recursions - 1, $suggestions);
}
elseif ($suggestions) {
// We may not recurse any more, but indicate the possibility to recurse.
$matches[$prefix . $name . ':'] = $wrapper->info();
if (!is_array($source) && $source instanceof EntityListWrapper) {
// Add some more possible list items.
for ($i = 1; $i < 4; $i++) {
$matches[$prefix . $i . ':'] = $wrapper->info();
}
}
}
}
}
return $matches;
}
/**
* Adds asserted metadata to the variable info. In case there are already
* assertions for a variable, the assertions are merged such that both apply.
*
* @see RulesData::applyMetadataAssertions()
*/
public static function addMetadataAssertions($var_info, $assertions) {
foreach ($assertions as $selector => $assertion) {
// Convert the selector back to underscores, such it matches the varname.
$selector = str_replace('-', '_', $selector);
$parts = explode(':', $selector);
if (isset($var_info[$parts[0]])) {
// Apply the selector to determine the right target array. We build an
// array like
// $var_info['rules assertion']['property1']['property2']['#info'] = ..
$target = &$var_info[$parts[0]]['rules assertion'];
foreach (array_slice($parts, 1) as $part) {
$target = &$target[$part];
}
// In case the assertion is directly for a variable, we have to modify
// the variable info directly. In case the asserted property is nested
// the info-has to be altered by RulesData::applyMetadataAssertions()
// before the child-wrapper is created.
if (count($parts) == 1) {
// Support asserting a type in case of generic entity references only.
if (isset($assertion['type']) && $var_info[$parts[0]]['type'] == 'entity') {
if (entity_get_info($assertion['type'])) {
$var_info[$parts[0]]['type'] = $assertion['type'];
}
unset($assertion['type']);
}
// Add any single bundle directly to the variable info, so the
// variable fits as argument for parameters requiring the bundle.
if (isset($assertion['bundle']) && count($bundles = (array) $assertion['bundle']) == 1) {
$var_info[$parts[0]]['bundle'] = reset($bundles);
}
}
// Add the assertions, but merge them with any previously added
// assertions if necessary.
$target['#info'] = isset($target['#info']) ? rules_update_array($target['#info'], $assertion) : $assertion;
// Add in a callback that the entity metadata wrapper pick up for
// altering the property info, such that we can add in the assertions.
$var_info[$parts[0]] += array('property info alter' => array('RulesData', 'applyMetadataAssertions'));
// In case there is a VARNAME_unchanged variable as it is used in update
// hooks, assume the assertions are valid for the unchanged variable
// too.
if (isset($var_info[$parts[0] . '_unchanged'])) {
$name = $parts[0] . '_unchanged';
$var_info[$name]['rules assertion'] = $var_info[$parts[0]]['rules assertion'];
$var_info[$name]['property info alter'] = array('RulesData', 'applyMetadataAssertions');
if (isset($var_info[$parts[0]]['bundle']) && !isset($var_info[$name]['bundle'])) {
$var_info[$name]['bundle'] = $var_info[$parts[0]]['bundle'];
}
}
}
}
return $var_info;
}
/**
* Property info alter callback for the entity metadata wrapper for applying
* the rules metadata assertions.
*
* @see RulesData::addMetadataAssertions()
*/
public static function applyMetadataAssertions(EntityMetadataWrapper $wrapper, $property_info) {
$info = $wrapper->info();
if (!empty($info['rules assertion'])) {
$assertion = $info['rules assertion'];
// In case there are list-wrappers pass through the assertions of the item
// but make sure we only apply the assertions for the list items for
// which the conditions are executed.
if (isset($info['parent']) && $info['parent'] instanceof EntityListWrapper) {
$assertion = isset($assertion[$info['name']]) ? $assertion[$info['name']] : array();
}
// Support specifying multiple bundles, whereas the added properties are
// the intersection of the bundle properties.
if (isset($assertion['#info']['bundle'])) {
$bundles = (array) $assertion['#info']['bundle'];
foreach ($bundles as $bundle) {
$properties[] = isset($property_info['bundles'][$bundle]['properties']) ? $property_info['bundles'][$bundle]['properties'] : array();
}
// Add the intersection.
$property_info['properties'] += count($properties) > 1 ? call_user_func_array('array_intersect_key', $properties) : reset($properties);
}
// Support adding directly asserted property info.
if (isset($assertion['#info']['property info'])) {
$property_info['properties'] += $assertion['#info']['property info'];
}
// Pass through any rules assertion of properties to their info, so any
// derived wrappers apply it.
foreach (element_children($assertion) as $key) {
$property_info['properties'][$key]['rules assertion'] = $assertion[$key];
$property_info['properties'][$key]['property info alter'] = array('RulesData', 'applyMetadataAssertions');
// Apply any 'type' and 'bundle' assertion directly to the propertyinfo.
if (isset($assertion[$key]['#info']['type'])) {
$type = $assertion[$key]['#info']['type'];
// Support asserting a type in case of generic entity references only.
if ($property_info['properties'][$key]['type'] == 'entity' && entity_get_info($type)) {
$property_info['properties'][$key]['type'] = $type;
}
}
if (isset($assertion[$key]['#info']['bundle'])) {
$bundle = (array) $assertion[$key]['#info']['bundle'];
// Add any single bundle directly to the variable info, so the
// property fits as argument for parameters requiring the bundle.
if (count($bundle) == 1) {
$property_info['properties'][$key]['bundle'] = reset($bundle);
}
}
}
}
return $property_info;
}
/**
* Property info alter callback for the entity metadata wrapper to inject
* metadata for the 'site' variable. In contrast to doing this via
* hook_rules_data_info() this callback makes use of the already existing
* property info cache for site information of entity metadata.
*
* @see RulesPlugin::availableVariables()
*/
public static function addSiteMetadata(EntityMetadataWrapper $wrapper, $property_info) {
$site_info = entity_get_property_info('site');
$property_info['properties'] += $site_info['properties'];
// Also invoke the usual callback for altering metadata, in case actions
// have specified further metadata.
return RulesData::applyMetadataAssertions($wrapper, $property_info);
}
}
/**
* A wrapper class similar to the EntityDrupalWrapper, but for non-entities.
*
* This class is intended to serve as base for a custom wrapper classes of
* identifiable data types, which are non-entities. By extending this class only
* the extractIdentifier() and load() methods have to be defined.
* In order to make the data type savable implement the
* RulesDataWrapperSavableInterface.
*
* That way it is possible for non-entity data types to be work with Rules, i.e.
* one can implement a 'ui class' with a direct input form returning the
* identifier of the data. However, instead of that it is suggested to implement
* an entity type, such that the same is achieved via general API functions like
* entity_load().
*/
abstract class RulesIdentifiableDataWrapper extends EntityStructureWrapper {
/**
* Contains the id.
*/
protected $id = FALSE;
/**
* Construct a new wrapper object.
*
* @param $type
* The type of the passed data.
* @param $data
* Optional. The data to wrap or its identifier.
* @param $info
* Optional. Used internally to pass info about properties down the tree.
*/
public function __construct($type, $data = NULL, $info = array()) {
parent::__construct($type, $data, $info);
$this->setData($data);
}
/**
* Sets the data internally accepting both the data id and object.
*/
protected function setData($data) {
if (isset($data) && $data !== FALSE && !is_object($data)) {
$this->id = $data;
$this->data = FALSE;
}
elseif (is_object($data)) {
// We got the data object passed.
$this->data = $data;
$id = $this->extractIdentifier($data);
$this->id = isset($id) ? $id : FALSE;
}
}
/**
* Returns the identifier of the wrapped data.
*/
public function getIdentifier() {
return $this->dataAvailable() && $this->value() ? $this->id : NULL;
}
/**
* Overridden.
*/
public function value(array $options = array()) {
$this->setData(parent::value());
if (!$this->data && !empty($this->id)) {
// Lazy load the data if necessary.
$this->data = $this->load($this->id);
if (!$this->data) {
throw new EntityMetadataWrapperException('Unable to load the ' . check_plain($this->type) . ' with the id ' . check_plain($this->id) . '.');
}
}
return $this->data;
}
/**
* Overridden to support setting the data by either the object or the id.
*/
public function set($value) {
if (!$this->validate($value)) {
throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.');
}
// As custom wrapper classes can only appear for Rules variables, but not
// as properties we don't have to care about updating the parent.
$this->clear();
$this->setData($value);
return $this;
}
/**
* Overridden.
*/
public function clear() {
$this->id = NULL;
parent::clear();
}
/**
* Prepare for serializiation.
*/
public function __sleep() {
$vars = parent::__sleep();
// Don't serialize the loaded data, except for the case the data is not
// saved yet.
if (!empty($this->id)) {
unset($vars['data']);
}
return $vars;
}
public function __wakeup() {
if ($this->id !== FALSE) {
// Make sure data is set, so the data will be loaded when needed.
$this->data = FALSE;
}
}
/**
* Extract the identifier of the given data object.
*
* @return
* The extracted identifier.
*/
abstract protected function extractIdentifier($data);
/**
* Load a data object given an identifier.
*
* @return
* The loaded data object, or FALSE if loading failed.
*/
abstract protected function load($id);
}
/**
* Interface that allows custom wrapper classes to declare that they are savable.
*/
interface RulesDataWrapperSavableInterface {
/**
* Save the currently wrapped data.
*/
public function save();
}