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