Projet

Général

Profil

Paste
Télécharger (52,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / rules / rules.module @ 76e2e7c3

1
<?php
2

    
3
/**
4
 * @file Rules engine module
5
 */
6

    
7
// Include our hook implementations early, as they can be called even before
8
// hook_init().
9
require_once dirname(__FILE__) . '/modules/events.inc';
10

    
11
/**
12
 * Implements hook_init().
13
 */
14
function rules_init() {
15
  rules_invoke_event('init');
16
}
17

    
18
/**
19
 * Returns an instance of the rules UI controller, which eases re-using the Rules UI.
20
 *
21
 * See the rules_admin.module for example usage.
22
 *
23
 * @return RulesUIController
24
 */
25
function rules_ui() {
26
  $static = drupal_static(__FUNCTION__);
27
  if (!isset($static)) {
28
    $static = new RulesUIController();
29
  }
30
  return $static;
31
}
32

    
33
/**
34
 * Returns a new rules action.
35
 *
36
 * @param $name
37
 *   The action's name.
38
 * @param $settings
39
 *   The action's settings array.
40
 * @return RulesAction
41
 */
42
function rules_action($name, $settings = array()) {
43
  return rules_plugin_factory('action', $name, $settings);
44
}
45

    
46
/**
47
 * Returns a new rules condition.
48
 *
49
 * @param $name
50
 *   The condition's name.
51
 * @param $settings
52
 *   The condition's settings array.
53
 * @return RulesCondition
54
 */
55
function rules_condition($name, $settings = array()) {
56
  return rules_plugin_factory('condition', $name, $settings);
57
}
58

    
59
/**
60
 * Creates a new rule.
61
 *
62
 * @param $variables
63
 *   The array of variables to setup in the evaluation state, making them
64
 *   available for the configuraion elements. Values for the variables need to
65
 *   be passed as argument when the rule is executed. Only Rule instances with
66
 *   no variables can be embedded in other configurations, e.g. rule sets.
67
 *   The array has to be keyed by variable name and contain a sub-array for each
68
 *   variable that has the same structure as the arrays used for describing
69
 *   parameters of an action, see hook_rules_action_info(). However, in addition
70
 *   to that the following keys are supported:
71
 *    - parameter: (optional) If set to FALSE, no parameter for the variable
72
 *      is created - thus no argument needs to be passed to the rule for the
73
 *      variable upon execution. As a consequence no value will be set
74
 *      initially, but the "Set data value" action may be used to do so. This is
75
 *      in particular useful for defining variables which can be provided to the
76
 *      caller (see $provides argument) but need not be passed in as parameter.
77
 * @param $provides
78
 *   The names of variables which should be provided to the caller. Only
79
 *   variables contained in $variables may be specified.
80
 * @return Rule
81
 */
82
function rule($variables = NULL, $provides = array()) {
83
  return rules_plugin_factory('rule', $variables, $provides);
84
}
85

    
86
/**
87
 * Creates a new reaction rule.
88
 *
89
 * @return RulesReactionRule
90
 */
91
function rules_reaction_rule() {
92
  return rules_plugin_factory('reaction rule');
93
}
94

    
95
/**
96
 * Creates a logical OR condition container.
97
 *
98
 * @param $variables
99
 *   An optional array as for rule().
100
 * @return RulesOr
101
 */
102
function rules_or($variables = NULL) {
103
  return rules_plugin_factory('or', $variables);
104
}
105

    
106
/**
107
 * Creates a logical AND condition container.
108
 *
109
 * @param $variables
110
 *   An optional array as for rule().
111
 * @return RulesAnd
112
 */
113
function rules_and($variables = NULL) {
114
  return rules_plugin_factory('and', $variables);
115
}
116

    
117
/**
118
 * Creates a loop.
119
 *
120
 * @param $settings
121
 *   The loop settings, containing
122
 *     'list:select': The data selector for the list to loop over.
123
 *     'item:var': Optionally a name for the list item variable.
124
 *     'item:label': Optionally a lebel for the list item variable.
125
 * @param $variables
126
 *   An optional array as for rule().
127
 * @return RulesLoop
128
 */
129
function rules_loop($settings = array(), $variables = NULL) {
130
  return rules_plugin_factory('loop', $settings, $variables);
131
}
132

    
133
/**
134
 * Creates a rule set.
135
 *
136
 * @param $variables
137
 *   An array as for rule().
138
 * @param $provides
139
 *   The names of variables which should be provided to the caller. See rule().
140
 * @return RulesRuleSet
141
 */
142
function rules_rule_set($variables = array(), $provides = array()) {
143
  return rules_plugin_factory('rule set', $variables, $provides);
144
}
145

    
146
/**
147
 * Creates an action set.
148
 *
149
 * @param $variables
150
 *   An array as for rule().
151
 * @param $provides
152
 *   The names of variables which should be provided to the caller. See rule().
153
 * @return RulesActionSet
154
 */
155
function rules_action_set($variables = array(), $provides = array()) {
156
  return rules_plugin_factory('action set', $variables, $provides);
157
}
158

    
159
/**
160
 * Log a message to the rules logger.
161
 *
162
 * @param $msg
163
 *   The message to log.
164
 * @param $args
165
 *   An array of placeholder arguments as used by t().
166
 * @param $priority
167
 *   A priority as defined by the RulesLog class.
168
 * @param RulesPlugin $element
169
 *  (optional) The RulesElement causing the log entry.
170
 * @param boolean $scope
171
 *  (optional) This may be used to denote the beginning (TRUE) or the end
172
 *  (FALSE) of a new execution scope.
173
 */
174
function rules_log($msg, $args = array(), $priority = RulesLog::INFO, RulesPlugin $element = NULL, $scope = NULL) {
175
  static $logger, $settings;
176

    
177
  // Statically cache the variable settings as this is called very often.
178
  if (!isset($settings)) {
179
    $settings['rules_log_errors'] = variable_get('rules_log_errors', RulesLog::WARN);
180
    $settings['rules_debug_log'] = variable_get('rules_debug_log', FALSE);
181
    $settings['rules_debug'] = variable_get('rules_debug', FALSE);
182
  }
183

    
184
  if ($priority >= $settings['rules_log_errors']) {
185
    $link = NULL;
186
    if (isset($element) && isset($element->root()->name)) {
187
      $link = l(t('edit configuration'), RulesPluginUI::path($element->root()->name, 'edit', $element));
188
    }
189
    watchdog('rules', $msg, $args, $priority == RulesLog::WARN ? WATCHDOG_WARNING : WATCHDOG_ERROR, $link);
190
  }
191
  // Do nothing in case debugging is totally disabled.
192
  if (!$settings['rules_debug_log'] && !$settings['rules_debug']) {
193
    return;
194
  }
195
  if (!isset($logger)) {
196
    $logger = RulesLog::logger();
197
  }
198
  $path = isset($element) && isset($element->root()->name) ? RulesPluginUI::path($element->root()->name, 'edit', $element) : NULL;
199
  $logger->log($msg, $args, $priority, $scope, $path);
200
}
201

    
202
/**
203
 * Fetches module definitions for the given hook name.
204
 *
205
 * Used for collecting events, rules, actions and condtions from other modules.
206
 *
207
 * @param $hook
208
 *   The hook of the definitions to get from invoking hook_rules_{$hook}.
209
 */
210
function rules_fetch_data($hook) {
211
  $data = &drupal_static(__FUNCTION__, array());
212
  static $discover = array(
213
    'action_info' => 'RulesActionHandlerInterface',
214
    'condition_info' => 'RulesConditionHandlerInterface',
215
    'event_info' => 'RulesEventHandlerInterface',
216
  );
217

    
218
  if (!isset($data[$hook])) {
219
    foreach (module_implements('rules_' . $hook) as $module) {
220
      $result = call_user_func($module . '_rules_' . $hook);
221
      if (isset($result) && is_array($result)) {
222
        foreach ($result as $name => $item) {
223
          $item += array('module' => $module);
224
          $data[$hook][$name] = $item;
225
        }
226
      }
227
    }
228
    // Support class discovery.
229
    if (isset($discover[$hook])) {
230
      $data[$hook] += rules_discover_plugins($discover[$hook]);
231
    }
232
    drupal_alter('rules_'. $hook, $data[$hook]);
233
  }
234
  return $data[$hook];
235
}
236

    
237
/**
238
 * Discover plugin implementations.
239
 *
240
 * Class based plugin handlers must be loaded when rules caches are rebuilt,
241
 * such that they get discovered properly. You have the following options:
242
 *  - Put it into a regular module file (discouraged)
243
 *  - Put it into your module.rules.inc file
244
 *  - Put it in any file and declare it using hook_rules_file_info()
245
 *  - Put it in any file and declare it using hook_rules_directory()
246
 *
247
 * In addition to that, the class must be loadable via regular class
248
 * auto-loading, thus put the file holding the class in your info file or use
249
 * another class-loader.
250
 *
251
 * @param string $class
252
 *   The class or interface the plugins must implement. For a plugin to be
253
 *   discovered it must have a static getInfo() method also.
254
 *
255
 * @return array
256
 *   An info-hook style array containing info about discovered plugins.
257
 *
258
 * @see RulesActionHandlerInterface
259
 * @see RulesConditionHandlerInterface
260
 * @see RulesEventHandlerInterface
261
 */
262
function rules_discover_plugins($class) {
263
  // Make sure all files possibly holding plugins are included.
264
  RulesAbstractPlugin::includeFiles();
265

    
266
  $items = array();
267
  foreach (get_declared_classes() as $plugin_class) {
268
    if (is_subclass_of($plugin_class, $class) && method_exists($plugin_class, 'getInfo')) {
269
      $info = call_user_func(array($plugin_class, 'getInfo'));
270
      $info['class'] = $plugin_class;
271
      $info['module'] = _rules_discover_module($plugin_class);
272
      $items[$info['name']] = $info;
273
    }
274
  }
275
  return $items;
276
}
277

    
278
/**
279
 * Determines the module providing the given class.
280
 */
281
function _rules_discover_module($class) {
282
  $paths = &drupal_static(__FUNCTION__);
283

    
284
  if (!isset($paths)) {
285
    // Build up a map of modules keyed by their directory.
286
    foreach (system_list('module_enabled') as $name => $module_info) {
287
      $paths[dirname($module_info->filename)] = $name;
288
    }
289
  }
290

    
291
  // Retrieve the class file and convert its absolute path to a regular Drupal
292
  // path relative to the installation root.
293
  $reflection = new ReflectionClass($class);
294
  $path = str_replace(realpath(DRUPAL_ROOT) . DIRECTORY_SEPARATOR, '', realpath(dirname($reflection->getFileName())));
295
  $path = DIRECTORY_SEPARATOR != '/' ? str_replace(DIRECTORY_SEPARATOR, '/', $path) : $path;
296

    
297
  // Go up the path until we match a module up.
298
  $parts = explode('/', $path);
299
  while (!isset($paths[$path]) && array_pop($parts)) {
300
    $path = dirname($path);
301
  }
302
  return isset($paths[$path]) ? $paths[$path] : FALSE;
303
}
304

    
305
/**
306
 * Gets a rules cache entry.
307
 */
308
function &rules_get_cache($cid = 'data') {
309
  // Make use of the fast, advanced drupal static pattern.
310
  static $drupal_static_fast;
311
  if (!isset($drupal_static_fast)) {
312
    $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__, array());
313
  }
314
  $cache = &$drupal_static_fast['cache'];
315

    
316
  if (!isset($cache[$cid])) {
317
    // The main 'data' cache includes translated strings, so each language is
318
    // cached separately.
319
    $cid_suffix = $cid == 'data' ? ':' . $GLOBALS['language']->language : '';
320

    
321
    if ($get = cache_get($cid . $cid_suffix, 'cache_rules')) {
322
      $cache[$cid] = $get->data;
323
    }
324
    elseif ($cid === 'data') {
325
      // There is no 'data' cache so we need to rebuild it. Make sure subsequent
326
      // cache gets of the main 'data' cache during rebuild get the interim
327
      // cache by passing in the reference of the static cache variable.
328
      _rules_rebuild_cache($cache['data']);
329
    }
330
    elseif (strpos($cid, 'comp_') === 0) {
331
      $cache[$cid] = FALSE;
332
      _rules_rebuild_component_cache();
333
    }
334
    elseif (strpos($cid, 'event_') === 0) {
335
      $cache[$cid] = FALSE;
336
      RulesEventSet::rebuildEventCache();
337
    }
338
    else {
339
      $cache[$cid] = FALSE;
340
    }
341
  }
342
  return $cache[$cid];
343
}
344

    
345
/**
346
 * Rebuilds the rules cache.
347
 *
348
 * This rebuilds the rules 'data' cache and invokes rebuildCache() methods on
349
 * all plugin classes, which allows plugins to add their own data to the cache.
350
 * The cache is rebuilt in the order the plugins are defined.
351
 *
352
 * Note that building the action/condition info cache triggers loading of all
353
 * components, thus depends on entity-loading and so syncing entities in code
354
 * to the database.
355
 *
356
 * @see rules_rules_plugin_info()
357
 * @see entity_defaults_rebuild()
358
 */
359
function _rules_rebuild_cache(&$cache) {
360
  foreach(array('data_info', 'plugin_info') as $hook) {
361
    $cache[$hook] = rules_fetch_data($hook);
362
  }
363
  foreach ($cache['plugin_info'] as $name => &$info) {
364
    // Let the items add something to the cache.
365
    $item = new $info['class']();
366
    $item->rebuildCache($info, $cache);
367
  }
368
  $cid_suffix = ':' . $GLOBALS['language']->language;
369
  cache_set('data' . $cid_suffix, $cache, 'cache_rules');
370
}
371

    
372
/**
373
 * Cache components to allow efficient usage via rules_invoke_component().
374
 *
375
 * @see rules_invoke_component()
376
 * @see rules_get_cache()
377
 */
378
function _rules_rebuild_component_cache() {
379
  $components = rules_get_components();
380

    
381
  foreach ($components as $id => $component) {
382
    // If a component is marked as dirty, check if this still applies.
383
    if ($component->dirty) {
384
      rules_config_update_dirty_flag($component);
385
    }
386
    if (!$component->dirty) {
387
      // Clone the component to avoid modules getting the to be cached
388
      // version from the static loading cache.
389
      $component = clone $component;
390
      $component->optimize();
391
      // Allow modules to alter the cached component.
392
      drupal_alter('rules_component', $component->plugin, $component);
393
      rules_set_cache('comp_' . $component->name, $component);
394
    }
395
  }
396
}
397

    
398
/**
399
 * Sets a rules cache item.
400
 *
401
 * In addition to calling cache_set(), this function makes sure the cache item
402
 * is immediately available via rules_get_cache() by keeping all cache items
403
 * in memory. That way we can guarantee rules_get_cache() is able to retrieve
404
 * any cache item, even if all cache gets fail.
405
 *
406
 * @see rules_get_cache()
407
 */
408
function rules_set_cache($cid, $data) {
409
  $cache = &drupal_static('rules_get_cache', array());
410
  $cache[$cid] = $data;
411
  cache_set($cid, $data, 'cache_rules');
412
}
413

    
414
/**
415
 * Implements hook_flush_caches().
416
 */
417
function rules_flush_caches() {
418
  return array('cache_rules');
419
}
420

    
421
/**
422
 * Clears the rule set cache
423
 */
424
function rules_clear_cache() {
425
  cache_clear_all('*', 'cache_rules', TRUE);
426
  variable_del('rules_event_whitelist');
427
  drupal_static_reset('rules_get_cache');
428
  drupal_static_reset('rules_fetch_data');
429
  drupal_static_reset('rules_config_update_dirty_flag');
430
  entity_get_controller('rules_config')->resetCache();
431
}
432

    
433
/**
434
 * Imports the given export and returns the imported configuration.
435
 *
436
 * @param $export
437
 *   A serialized string in JSON format as produced by the RulesPlugin::export()
438
 *   method, or the PHP export as usual PHP array.
439
 * @return RulesPlugin
440
 */
441
function rules_import($export, &$error_msg = '') {
442
  return entity_get_controller('rules_config')->import($export, $error_msg);
443
}
444

    
445

    
446
/**
447
 * Wraps the given data.
448
 *
449
 * @param $data
450
 *   If available, the actual data, else NULL.
451
 * @param $info
452
 *   An array of info about this data.
453
 * @param $force
454
 *   Usually data is only wrapped if really needed. If set to TRUE, wrapping the
455
 *   data is forced, so primitive data types are also wrapped.
456
 * @return EntityMetadataWrapper
457
 *   An EntityMetadataWrapper or the unwrapped data.
458
 *
459
 * @see hook_rules_data_info()
460
 */
461
function &rules_wrap_data($data = NULL, $info, $force = FALSE) {
462
  // If the data is already wrapped, use the existing wrapper.
463
  if ($data instanceof EntityMetadataWrapper) {
464
    return $data;
465
  }
466
  $cache = rules_get_cache();
467
  // Define the keys to be passed through to the metadata wrapper.
468
  $wrapper_keys = array_flip(array('property info', 'property defaults'));
469
  if (isset($cache['data_info'][$info['type']])) {
470
    $info += array_intersect_key($cache['data_info'][$info['type']], $wrapper_keys);
471
  }
472
  // If a list is given, also add in the info of the item type.
473
  $list_item_type = entity_property_list_extract_type($info['type']);
474
  if ($list_item_type && isset($cache['data_info'][$list_item_type])) {
475
    $info += array_intersect_key($cache['data_info'][$list_item_type], $wrapper_keys);
476
  }
477
  // By default we do not wrap the data, except for completely unknown types.
478
  if (!empty($cache['data_info'][$info['type']]['wrap']) || $list_item_type || $force || empty($cache['data_info'][$info['type']])) {
479
    unset($info['handler']);
480
    // Allow data types to define custom wrapper classes.
481
    if (!empty($cache['data_info'][$info['type']]['wrapper class'])) {
482
      $class = $cache['data_info'][$info['type']]['wrapper class'];
483
      $wrapper = new $class($info['type'], $data, $info);
484
    }
485
    else {
486
      $wrapper = entity_metadata_wrapper($info['type'], $data, $info);
487
    }
488
    return $wrapper;
489
  }
490
  return $data;
491
}
492

    
493
/**
494
 * Unwraps the given data, if it's wrapped.
495
 *
496
 * @param $data
497
 *   An array of wrapped data.
498
 * @param $info
499
 *   Optionally an array of info about how to unwrap the data. Keyed as $data.
500
 * @return
501
 *   An array containing unwrapped or passed through data.
502
 */
503
function rules_unwrap_data(array $data, $info = array()) {
504
  $cache = rules_get_cache();
505
  foreach ($data as $key => $entry) {
506
    // If it's a wrapper, unwrap unless specified otherwise.
507
    if ($entry instanceof EntityMetadataWrapper) {
508
      if (!isset($info[$key]['allow null'])) {
509
        $info[$key]['allow null'] = FALSE;
510
      }
511
      if (!isset($info[$key]['wrapped'])) {
512
        // By default, do not unwrap special data types that are always wrapped.
513
        $info[$key]['wrapped'] = (isset($info[$key]['type']) && is_string($info[$key]['type']) && !empty($cache['data_info'][$info[$key]['type']]['is wrapped']));
514
      }
515
      // Activate the decode option by default if 'sanitize' is not enabled, so
516
      // any text is either sanitized or decoded.
517
      // @see EntityMetadataWrapper::value()
518
      $options = $info[$key] + array('decode' => empty($info[$key]['sanitize']));
519

    
520
      try {
521
        if (!($info[$key]['allow null'] && $info[$key]['wrapped'])) {
522
          $value = $entry->value($options);
523

    
524
          if (!$info[$key]['wrapped']) {
525
            $data[$key] = $value;
526
          }
527
          if (!$info[$key]['allow null'] && !isset($value)) {
528
            throw new RulesEvaluationException('The variable or parameter %name is empty.', array('%name' => $key));
529
          }
530
        }
531
      }
532
      catch (EntityMetadataWrapperException $e) {
533
        throw new RulesEvaluationException('Unable to get the data value for the variable or parameter %name. Error: !error', array('%name' => $key, '!error' => $e->getMessage()));
534
      }
535
    }
536
  }
537
  return $data;
538
}
539

    
540
/**
541
 * Gets event info for a given event.
542
 *
543
 * @param string $event_name
544
 *   A (configured) event name.
545
 *
546
 * @return array
547
 *   An array of event info. If the event is unknown, a suiting info array is
548
 *   generated and returned
549
 */
550
function rules_get_event_info($event_name) {
551
  $base_event_name = rules_get_event_base_name($event_name);
552
  $events = rules_fetch_data('event_info');
553
  if (isset($events[$base_event_name])) {
554
    return $events[$base_event_name] + array('name' => $base_event_name);
555
  }
556
  return array(
557
    'label' => t('Unknown event "!event_name"', array('!event_name' => $base_event_name)),
558
    'name' => $base_event_name,
559
  );
560
}
561

    
562
/**
563
 * Returns the base name of a configured event name.
564
 *
565
 * For a configured event name like node_view--article the base event name
566
 * node_view is returned.
567
 *
568
 * @return string
569
 *   The event base name.
570
 */
571
function rules_get_event_base_name($event_name) {
572
  // Cut off any suffix from a configured event name.
573
  if (strpos($event_name, '--') !== FALSE) {
574
    $parts = explode('--', $event_name, 2);
575
    return $parts[0];
576
  }
577
  return $event_name;
578
}
579

    
580
/**
581
 * Returns the rule event handler for the given event.
582
 *
583
 * Events having no settings are handled via the class RulesEventSettingsNone.
584
 *
585
 * @param string $event_name
586
 *   The event name (base or configured).
587
 * @param array $settings
588
 *   (optional) An array of event settings to set on the handler.
589
 *
590
 * @return RulesEventHandlerInterface
591
 *   The event handler.
592
 */
593
function rules_get_event_handler($event_name, array $settings = NULL) {
594
  $event_name = rules_get_event_base_name($event_name);
595
  $event_info = rules_get_event_info($event_name);
596
  $class = !empty($event_info['class']) ? $event_info['class'] : 'RulesEventDefaultHandler';
597
  $handler = new $class($event_name, $event_info);
598
  return isset($settings) ? $handler->setSettings($settings) : $handler;
599
}
600

    
601
/**
602
 * Creates a new instance of a the given rules plugin.
603
 *
604
 * @return RulesPlugin
605
 */
606
function rules_plugin_factory($plugin_name, $arg1 = NULL, $arg2 = NULL) {
607
  $cache = rules_get_cache();
608
  if (isset($cache['plugin_info'][$plugin_name]['class'])) {
609
    return new $cache['plugin_info'][$plugin_name]['class']($arg1, $arg2);
610
  }
611
}
612

    
613
/**
614
 * Implementation of hook_rules_plugin_info().
615
 *
616
 * Note that the cache is rebuilt in the order of the plugins. Therefore the
617
 * condition and action plugins must be at the top, so that any components
618
 * re-building their cache can create configurations including properly setup-ed
619
 * actions and conditions.
620
 */
621
function rules_rules_plugin_info() {
622
  return array(
623
    'condition' => array(
624
      'class' => 'RulesCondition',
625
      'embeddable' => 'RulesConditionContainer',
626
      'extenders' => array (
627
        'RulesPluginImplInterface' => array(
628
          'class' => 'RulesAbstractPluginDefaults',
629
        ),
630
        'RulesPluginFeaturesIntegrationInterace' => array(
631
          'methods' => array(
632
            'features_export' => 'rules_features_abstract_default_features_export',
633
          ),
634
        ),
635
        'RulesPluginUIInterface' => array(
636
          'class' => 'RulesAbstractPluginUI',
637
        ),
638
      ),
639
    ),
640
    'action' => array(
641
      'class' => 'RulesAction',
642
      'embeddable' => 'RulesActionContainer',
643
      'extenders' => array (
644
        'RulesPluginImplInterface' => array(
645
          'class' => 'RulesAbstractPluginDefaults',
646
        ),
647
        'RulesPluginFeaturesIntegrationInterace' => array(
648
          'methods' => array(
649
            'features_export' => 'rules_features_abstract_default_features_export',
650
          ),
651
        ),
652
        'RulesPluginUIInterface' => array(
653
          'class' => 'RulesAbstractPluginUI',
654
        ),
655
      ),
656
    ),
657
    'or' => array(
658
      'label' => t('Condition set (OR)'),
659
      'class' => 'RulesOr',
660
      'embeddable' => 'RulesConditionContainer',
661
      'component' => TRUE,
662
      'extenders' => array(
663
        'RulesPluginUIInterface' => array(
664
          'class' => 'RulesConditionContainerUI',
665
        ),
666
      ),
667
    ),
668
    'and' => array(
669
      'label' => t('Condition set (AND)'),
670
      'class' => 'RulesAnd',
671
      'embeddable' => 'RulesConditionContainer',
672
      'component' => TRUE,
673
      'extenders' => array(
674
        'RulesPluginUIInterface' => array(
675
          'class' => 'RulesConditionContainerUI',
676
        ),
677
      ),
678
    ),
679
    'action set' => array(
680
      'label' => t('Action set'),
681
      'class' => 'RulesActionSet',
682
      'embeddable' => FALSE,
683
      'component' => TRUE,
684
      'extenders' => array(
685
        'RulesPluginUIInterface' => array(
686
          'class' => 'RulesActionContainerUI',
687
        ),
688
      ),
689
    ),
690
    'rule' => array(
691
      'label' => t('Rule'),
692
      'class' => 'Rule',
693
      'embeddable' => 'RulesRuleSet',
694
      'component' => TRUE,
695
      'extenders' => array(
696
        'RulesPluginUIInterface' => array(
697
          'class' => 'RulesRuleUI',
698
        ),
699
      ),
700
    ),
701
    'loop' => array(
702
      'class' => 'RulesLoop',
703
      'embeddable' => 'RulesActionContainer',
704
      'extenders' => array(
705
        'RulesPluginUIInterface' => array(
706
          'class' => 'RulesLoopUI',
707
        ),
708
      ),
709
    ),
710
    'reaction rule' => array(
711
      'class' => 'RulesReactionRule',
712
      'embeddable' => FALSE,
713
      'extenders' => array(
714
        'RulesPluginUIInterface' => array(
715
          'class' => 'RulesReactionRuleUI',
716
        ),
717
      ),
718
    ),
719
    'event set' => array(
720
      'class' => 'RulesEventSet',
721
      'embeddable' => FALSE,
722
    ),
723
    'rule set' => array(
724
      'label' => t('Rule set'),
725
      'class' => 'RulesRuleSet',
726
      'component' => TRUE,
727
      // Rule sets don't get embedded - we use a separate action to execute.
728
      'embeddable' => FALSE,
729
      'extenders' => array(
730
        'RulesPluginUIInterface' => array(
731
          'class' => 'RulesRuleSetUI',
732
        ),
733
      ),
734
    ),
735
  );
736
}
737

    
738
/**
739
 * Implementation of hook_entity_info().
740
 */
741
function rules_entity_info() {
742
  return array(
743
    'rules_config' => array(
744
      'label' => t('Rules configuration'),
745
      'controller class' => 'RulesEntityController',
746
      'base table' => 'rules_config',
747
      'fieldable' => TRUE,
748
      'entity keys' => array(
749
        'id' => 'id',
750
        'name' => 'name',
751
        'label' => 'label',
752
      ),
753
      'module' => 'rules',
754
      'static cache' => TRUE,
755
      'bundles' => array(),
756
      'configuration' => TRUE,
757
      'exportable' => TRUE,
758
      'export' => array(
759
        'default hook' => 'default_rules_configuration',
760
      ),
761
      'access callback' => 'rules_config_access',
762
      'features controller class' => 'RulesFeaturesController',
763
    ),
764
  );
765
}
766

    
767
/**
768
 * Implementation of hook_hook_info().
769
 */
770
function rules_hook_info() {
771
  foreach(array('plugin_info', 'rules_directory', 'data_info', 'condition_info', 'action_info', 'event_info', 'file_info', 'evaluator_info', 'data_processor_info') as $hook) {
772
    $hooks['rules_' . $hook] = array(
773
      'group' => 'rules',
774
    );
775
    $hooks['rules_' . $hook . '_alter'] = array(
776
      'group' => 'rules',
777
    );
778
  }
779
  $hooks['default_rules_configuration'] = array(
780
    'group' => 'rules_defaults',
781
  );
782
  $hooks['default_rules_configuration_alter'] = array(
783
    'group' => 'rules_defaults',
784
  );
785
  return $hooks;
786
}
787

    
788
/**
789
 * Load rule configurations from the database.
790
 *
791
 * This function should be used whenever you need to load more than one entity
792
 * from the database. The entities are loaded into memory and will not require
793
 * database access if loaded again during the same page request.
794
 *
795
 * @see hook_entity_info()
796
 * @see RulesEntityController
797
 *
798
 * @param $names
799
 *   An array of rules configuration names or FALSE to load all.
800
 * @param $conditions
801
 *   An array of conditions in the form 'field' => $value.
802
 *
803
 * @return
804
 *   An array of rule configurations indexed by their ids.
805
 */
806
function rules_config_load_multiple($names = array(), $conditions = array()) {
807
  return entity_load_multiple_by_name('rules_config', $names, $conditions);
808
}
809

    
810
/**
811
 * Loads a single rule configuration from the database.
812
 *
813
 * @see rules_config_load_multiple()
814
 *
815
 * @return RulesPlugin
816
 */
817
function rules_config_load($name) {
818
  return entity_load_single('rules_config', $name);
819
}
820

    
821
/**
822
 * Returns an array of configured components.
823
 *
824
 * For actually executing a component use rules_invoke_component(), as this
825
 * retrieves the component from cache instead.
826
 *
827
 * @param $label
828
 *   Whether to return only the label or the whole component object.
829
 * @param $type
830
 *   Optionally filter for 'action' or 'condition' components.
831
 * @param $conditions
832
 *   An array of additional conditions as required by rules_config_load().
833
 *
834
 * @return
835
 *   An array keyed by component name containing either the label or the config.
836
 */
837
function rules_get_components($label = FALSE, $type = NULL, $conditions = array()) {
838
  $cache = rules_get_cache();
839
  $plugins = array_keys(rules_filter_array($cache['plugin_info'], 'component', TRUE));
840
  $conditions = $conditions + array('plugin' => $plugins);
841
  $faces = array(
842
    'action' => 'RulesActionInterface',
843
    'condition' => 'RulesConditionInterface',
844
  );
845
  $items = array();
846
  foreach (rules_config_load_multiple(FALSE, $conditions) as $name => $config) {
847
    if (!isset($type) || $config instanceof $faces[$type]) {
848
      $items[$name] = $label ? $config->label() : $config;
849
    }
850
  }
851
  return $items;
852
}
853

    
854
/**
855
 * Delete rule configurations from database.
856
 *
857
 * @param $ids
858
 *   An array of entity IDs.
859
 */
860
function rules_config_delete(array $ids) {
861
  return entity_get_controller('rules_config')->delete($ids);
862
}
863

    
864
/**
865
 * Ensures the configuration's 'dirty' flag is up to date by running an integrity check.
866
 *
867
 * @param $update
868
 *   (optional) Whether the dirty flag is also updated in the database if
869
 *   necessary. Defaults to TRUE.
870
 */
871
function rules_config_update_dirty_flag($rules_config, $update = TRUE) {
872
  // Keep a log of already check configurations to avoid repetitive checks on
873
  // oftent used components.
874
  // @see rules_element_invoke_component_validate()
875
  $checked = &drupal_static(__FUNCTION__, array());
876
  if (!empty($checked[$rules_config->name])) {
877
    return;
878
  }
879
  $checked[$rules_config->name] = TRUE;
880

    
881
  $was_dirty = !empty($rules_config->dirty);
882
  try {
883
    // First set the rule to dirty, so any repetitive checks give green light
884
    // for this configuration.
885
    $rules_config->dirty = FALSE;
886
    $rules_config->integrityCheck();
887
    if ($was_dirty) {
888
      $variables = array('%label' => $rules_config->label(), '%name' => $rules_config->name, '@plugin' => $rules_config->plugin());
889
      watchdog('rules', 'The @plugin %label (%name) was marked dirty, but passes the integrity check now and is active again.', $variables, WATCHDOG_INFO);
890
    }
891
  }
892
  catch (RulesIntegrityException $e) {
893
    $rules_config->dirty = TRUE;
894
    if (!$was_dirty) {
895
      $variables = array('%label' => $rules_config->label(), '%name' => $rules_config->name, '!message' => $e->getMessage(), '@plugin' => $rules_config->plugin());
896
      watchdog('rules', 'The @plugin %label (%name) fails the integrity check and cannot be executed. Error: !message', $variables, WATCHDOG_ERROR);
897
    }
898
  }
899
  // Save the updated dirty flag to the database.
900
  if ($was_dirty != $rules_config->dirty) {
901
    db_update('rules_config')
902
      ->fields(array('dirty' => (int) $rules_config->dirty))
903
      ->condition('id', $rules_config->id)
904
      ->execute();
905
  }
906
}
907

    
908
/**
909
 * Invokes a hook and the associated rules event.
910
 *
911
 * Calling this function does the same as calling module_invoke_all() and
912
 * rules_invoke_event() separately, however merges both functions into one in
913
 * order to ease usage and to work efficiently.
914
 *
915
 * @param $hook
916
 *   The name of the hook / event to invoke.
917
 * @param ...
918
 *   Arguments to pass to the hook / event.
919
 *
920
 * @return
921
 *   An array of return values of the hook implementations. If modules return
922
 *   arrays from their implementations, those are merged into one array.
923
 */
924
function rules_invoke_all() {
925
  // Copied code from module_invoke_all().
926
  $args = func_get_args();
927
  $hook = $args[0];
928
  unset($args[0]);
929
  $return = array();
930
  foreach (module_implements($hook) as $module) {
931
    $function = $module . '_' . $hook;
932
    if (function_exists($function)) {
933
      $result = call_user_func_array($function, $args);
934
      if (isset($result) && is_array($result)) {
935
        $return = array_merge_recursive($return, $result);
936
      }
937
      elseif (isset($result)) {
938
        $return[] = $result;
939
      }
940
    }
941
  }
942
  // Invoke the event.
943
  rules_invoke_event_by_args($hook, $args);
944

    
945
  return $return;
946
}
947

    
948
/**
949
 * Invokes configured rules for the given event.
950
 *
951
 * @param $event_name
952
 *   The event's name.
953
 * @param ...
954
 *   Pass parameters for the variables provided by this event, as defined in
955
 *   hook_rules_event_info(). Example given:
956
 *   @code
957
 *     rules_invoke_event('node_view', $node, $view_mode);
958
 *   @endcode
959
 *
960
 * @see rules_invoke_event_by_args()
961
 */
962
function rules_invoke_event() {
963
  global $conf;
964

    
965
  $args = func_get_args();
966
  $event_name = $args[0];
967
  unset($args[0]);
968
  // We maintain a whitelist of configured events to reduces the number of cache
969
  // reads. We access it directly via the global $conf as this is fast without
970
  // having to introduce another static cache. Then, if the whitelist is unset,
971
  // we ignore it so cache rebuilding is triggered.
972
  if (!defined('MAINTENANCE_MODE') && (!isset($conf['rules_event_whitelist']) || isset($conf['rules_event_whitelist'][$event_name])) && $event = rules_get_cache('event_' . $event_name)) {
973
    $event->executeByArgs($args);
974
  }
975
}
976

    
977
/**
978
 * Invokes configured rules for the given event.
979
 *
980
 * @param $event_name
981
 *   The event's name.
982
 * @param $args
983
 *   An array of parameters for the variables provided by the event, as defined
984
 *   in hook_rules_event_info(). Either pass an array keyed by the variable
985
 *   names or a numerically indexed array, in which case the ordering of the
986
 *   passed parameters has to match the order of the specified variables.
987
 *   Example given:
988
 *   @code
989
 *     rules_invoke_event_by_args('node_view', array('node' => $node, 'view_mode' => $view_mode));
990
 *   @endcode
991
 *
992
 * @see rules_invoke_event()
993
 */
994
function rules_invoke_event_by_args($event_name, $args = array()) {
995
  global $conf;
996

    
997
  // We maintain a whitelist of configured events to reduces the number of cache
998
  // reads. We access it directly via the global $conf as this is fast without
999
  // having to introduce another static cache. Then, if the whitelist is unset,
1000
  // we ignore it so cache rebuilding is triggered.
1001
  if (!defined('MAINTENANCE_MODE') && (!isset($conf['rules_event_whitelist']) || isset($conf['rules_event_whitelist'][$event_name])) && $event = rules_get_cache('event_' . $event_name)) {
1002
    $event->executeByArgs($args);
1003
  }
1004
}
1005

    
1006
/**
1007
 * Invokes a rule component, e.g. a rule set.
1008
 *
1009
 * @param $component_name
1010
 *   The component's name.
1011
 * @param $args
1012
 *   Pass further parameters as required for the invoked component.
1013
 *
1014
 * @return
1015
 *   An array of variables as provided by the component, or FALSE in case the
1016
 *   component could not be executed.
1017
 */
1018
function rules_invoke_component() {
1019
  $args = func_get_args();
1020
  $name = array_shift($args);
1021
  if ($component = rules_get_cache('comp_' . $name)) {
1022
    return $component->executeByArgs($args);
1023
  }
1024
  return FALSE;
1025
}
1026

    
1027
/**
1028
 * Filters the given array of arrays by keeping only entries which have $key set
1029
 * to the value of $value.
1030
 *
1031
 * @param $array
1032
 *   The array of arrays to filter.
1033
 * @param $key
1034
 *   The key used for the comparison.
1035
 * @param $value
1036
 *   The value to compare the array's entry to.
1037
 *
1038
 * @return array
1039
 *   The filtered array.
1040
 */
1041
function rules_filter_array($array, $key, $value) {
1042
  $return = array();
1043
  foreach ($array as $i => $entry) {
1044
    $entry += array($key => NULL);
1045
    if ($entry[$key] == $value) {
1046
      $return[$i] = $entry;
1047
    }
1048
  }
1049
  return $return;
1050
}
1051

    
1052
/**
1053
 * Merges the $update array into $array making sure no values of $array not
1054
 * appearing in $update are lost.
1055
 *
1056
 * @return
1057
 *   The updated array.
1058
 */
1059
function rules_update_array(array $array, array $update) {
1060
  foreach ($update as $key => $data) {
1061
    if (isset($array[$key]) && is_array($array[$key]) && is_array($data)) {
1062
      $array[$key] = rules_update_array($array[$key], $data);
1063
    }
1064
    else {
1065
      $array[$key] = $data;
1066
    }
1067
  }
1068
  return $array;
1069
}
1070

    
1071
/**
1072
 * Extracts the property with the given name.
1073
 *
1074
 * @param $arrays
1075
 *   An array of arrays from which a property is to be extracted.
1076
 * @param $key
1077
 *   The name of the property to extract.
1078
 *
1079
 * @return An array of extracted properties, keyed as in $arrays-
1080
 */
1081
function rules_extract_property($arrays, $key) {
1082
  $data = array();
1083
  foreach ($arrays as $name => $item) {
1084
    $data[$name] = $item[$key];
1085
  }
1086
  return $data;
1087
}
1088

    
1089
/**
1090
 * Returns the first key of the array.
1091
 */
1092
function rules_array_key($array) {
1093
  reset($array);
1094
  return key($array);
1095
}
1096

    
1097
/**
1098
 * Clean replacements so they are URL friendly.
1099
 *
1100
 * Can be used as 'cleaning callback' for action or condition parameters.
1101
 *
1102
 * @param $replacements
1103
 *   An array of token replacements that need to be "cleaned" for use in the URL.
1104
 * @param $data
1105
 *   An array of objects used to generate the replacements.
1106
 * @param $options
1107
 *   An array of options used to generate the replacements.
1108
 *
1109
 * @see rules_path_action_info()
1110
 */
1111
function rules_path_clean_replacement_values(&$replacements, $data = array(), $options = array()) {
1112
  // Include path.eval.inc which contains path cleaning functions.
1113
  module_load_include('inc', 'rules', 'modules/path.eval');
1114
  foreach ($replacements as $token => $value) {
1115
    $replacements[$token] = rules_clean_path($value);
1116
  }
1117
}
1118

    
1119
/**
1120
 * Implements hook_theme().
1121
 */
1122
function rules_theme() {
1123
  return array(
1124
    'rules_elements' => array(
1125
      'render element' => 'element',
1126
      'file' => 'ui/ui.theme.inc',
1127
    ),
1128
    'rules_content_group' => array(
1129
      'render element' => 'element',
1130
      'file' => 'ui/ui.theme.inc',
1131
    ),
1132
    'rules_parameter_configuration' => array(
1133
      'render element' => 'element',
1134
      'file' => 'ui/ui.theme.inc',
1135
    ),
1136
    'rules_variable_view' => array(
1137
      'render element' => 'element',
1138
      'file' => 'ui/ui.theme.inc',
1139
    ),
1140
    'rules_data_selector_help' => array(
1141
      'variables' => array('parameter' => NULL, 'variables' => NULL),
1142
      'file' => 'ui/ui.theme.inc',
1143
    ),
1144
    'rules_ui_variable_form' => array(
1145
      'render element' => 'element',
1146
      'file' => 'ui/ui.theme.inc',
1147
    ),
1148
    'rules_log' => array(
1149
      'render element' => 'element',
1150
      'file' => 'ui/ui.theme.inc',
1151
    ),
1152
    'rules_autocomplete' => array(
1153
      'render element' => 'element',
1154
      'file' => 'ui/ui.theme.inc',
1155
    ),
1156
    'rules_debug_element' => array(
1157
      'render element' => 'element',
1158
      'file' => 'ui/ui.theme.inc',
1159
    ),
1160
    'rules_settings_help' => array(
1161
      'variables' => array('text' => '', 'heading' => ''),
1162
      'file' => 'ui/ui.theme.inc',
1163
    ),
1164
  );
1165
}
1166

    
1167
/**
1168
 * Implements hook_permission().
1169
 */
1170
function rules_permission() {
1171
  $perms = array(
1172
    'administer rules' => array(
1173
      'title' => t('Administer rule configurations'),
1174
      'description' => t('Administer rule configurations including events, conditions and actions for which the user has sufficient access permissions.'),
1175
    ),
1176
    'bypass rules access' => array(
1177
      'title' => t('Bypass Rules access control'),
1178
      'description' => t('Control all configurations regardless of permission restrictions of events, conditions or actions.'),
1179
      'restrict access' => TRUE,
1180
    ),
1181
    'access rules debug' => array(
1182
      'title' => t('Access the Rules debug log'),
1183
    ),
1184
  );
1185

    
1186
  // Fetch all components to generate the access keys.
1187
  $conditions['plugin'] = array_keys(rules_filter_array(rules_fetch_data('plugin_info'), 'component', TRUE));
1188
  $conditions['access_exposed'] = 1;
1189
  $components = entity_load('rules_config', FALSE, $conditions);
1190
  $perms += rules_permissions_by_component($components);
1191

    
1192
  return $perms;
1193
}
1194

    
1195
/**
1196
 * Helper function to get all the permissions for components that have access exposed.
1197
 */
1198
function rules_permissions_by_component(array $components = array()) {
1199
  $perms = array();
1200
  foreach ($components as $component) {
1201
    $perms += array(
1202
      "use Rules component $component->name" => array(
1203
        'title' => t('Use Rules component %component', array('%component' => $component->label())),
1204
        'description' => t('Controls access for using the component %component via the provided action or condition. <a href="@component-edit-url">Edit this component.</a>', array('%component' => $component->label(), '@component-edit-url' => url(RulesPluginUI::path($component->name)))),
1205
      ),
1206
    );
1207
  }
1208
  return $perms;
1209
}
1210

    
1211
/**
1212
 * Menu callback for loading rules configuration elements.
1213
 * @see RulesUIController::config_menu()
1214
 */
1215
function rules_element_load($element_id, $config_name) {
1216
  $config = rules_config_load($config_name);
1217
  return $config->elementMap()->lookup($element_id);
1218
}
1219

    
1220
/**
1221
 * Menu callback for getting the title as configured.
1222
 * @see RulesUIController::config_menu()
1223
 */
1224
function rules_get_title($text, $element) {
1225
  if ($element instanceof RulesPlugin) {
1226
    $cache = rules_get_cache();
1227
    $plugin = $element->plugin();
1228
    $plugin = isset($cache['plugin_info'][$plugin]['label']) ? $cache['plugin_info'][$plugin]['label'] : $plugin;
1229
    $plugin = drupal_strtolower(drupal_substr($plugin, 0, 1)) . drupal_substr($plugin, 1);
1230
    return t($text, array('!label' => $element->label(), '!plugin' => $plugin));
1231
  }
1232
  // As fallback treat $element as simple string.
1233
  return t($text, array('!plugin' => $element));
1234
}
1235

    
1236
/**
1237
 * Menu callback for getting the title for the add element page.
1238
 *
1239
 * Uses a work-a-round for accessing the plugin name.
1240
 * @see RulesUIController::config_menu()
1241
 */
1242
function rules_menu_add_element_title($array) {
1243
  $plugin_name = arg($array[0]);
1244
  $cache = rules_get_cache();
1245
  if (isset($cache['plugin_info'][$plugin_name]['class'])) {
1246
    $info = $cache['plugin_info'][$plugin_name] + array('label' => $plugin_name);
1247
    $label = drupal_strtolower(drupal_substr($info['label'], 0, 1)) . drupal_substr($info['label'], 1);
1248
    return t('Add a new !plugin', array('!plugin' => $label));
1249
  }
1250
}
1251

    
1252
/**
1253
 * Returns the current region for the debug log.
1254
 */
1255
function rules_debug_log_region() {
1256
  // If there is no setting for the current theme use the default theme setting.
1257
  global $theme_key;
1258
  $theme_default = variable_get('theme_default', 'bartik');
1259
  return variable_get('rules_debug_region_' . $theme_key, variable_get('rules_debug_region_' . $theme_default, 'help'));
1260
}
1261

    
1262
/**
1263
 * Implements hook_page_build() to add the rules debug log to the page bottom.
1264
 */
1265
function rules_page_build(&$page) {
1266
  // Invoke a the page redirect, in case the action has been executed.
1267
  // @see rules_action_drupal_goto()
1268
  if (isset($GLOBALS['_rules_action_drupal_goto_do'])) {
1269
    list($url, $force) = $GLOBALS['_rules_action_drupal_goto_do'];
1270
    drupal_goto($url);
1271
  }
1272

    
1273
  if (isset($_SESSION['rules_debug'])) {
1274
    $region = rules_debug_log_region();
1275
    foreach ($_SESSION['rules_debug'] as $log) {
1276
      $page[$region]['rules_debug'][] = array(
1277
        '#markup' => $log,
1278
      );
1279
      $page[$region]['rules_debug']['#theme_wrappers'] = array('rules_log');
1280
    }
1281
    unset($_SESSION['rules_debug']);
1282
  }
1283

    
1284
  if (rules_show_debug_output()) {
1285
    $region = rules_debug_log_region();
1286
    $page[$region]['rules_debug']['#pre_render'] = array('rules_debug_log_pre_render');
1287
  }
1288
}
1289

    
1290
/**
1291
 * Pre-render callback for the debug log, which renders and then clears it.
1292
 */
1293
function rules_debug_log_pre_render($elements) {
1294
  $logger = RulesLog::logger();
1295
  if ($log = $logger->render()) {
1296
    $logger = RulesLog::logger();
1297
    $logger->clear();
1298
    $elements[] = array('#markup' => $log);
1299
    $elements['#theme_wrappers'] = array('rules_log');
1300
    // Log the rules log to the system log if enabled.
1301
    if (variable_get('rules_debug_log', FALSE)) {
1302
      watchdog('rules', 'Rules debug information: !log', array('!log' => $log), WATCHDOG_NOTICE);
1303
    }
1304
  }
1305
  return $elements;
1306
}
1307

    
1308
/**
1309
 * Implements hook_drupal_goto_alter().
1310
 *
1311
 * @see rules_action_drupal_goto()
1312
 */
1313
function rules_drupal_goto_alter(&$path, &$options, &$http_response_code) {
1314
  // Invoke a the page redirect, in case the action has been executed.
1315
  if (isset($GLOBALS['_rules_action_drupal_goto_do'])) {
1316
    list($url, $force) = $GLOBALS['_rules_action_drupal_goto_do'];
1317

    
1318
    if ($force || !isset($_GET['destination'])) {
1319
      $url = drupal_parse_url($url);
1320
      $path = $url['path'];
1321
      $options['query'] = $url['query'];
1322
      $options['fragment'] = $url['fragment'];
1323
      $http_response_code = 302;
1324
    }
1325
  }
1326
}
1327

    
1328
/**
1329
 * Returns whether the debug log should be shown.
1330
 */
1331
function rules_show_debug_output() {
1332
  if (variable_get('rules_debug', FALSE) == RulesLog::INFO && user_access('access rules debug')) {
1333
    return TRUE;
1334
  }
1335
  // For performance avoid unnecessary auto-loading of the RulesLog class.
1336
  return variable_get('rules_debug', FALSE) == RulesLog::WARN && user_access('access rules debug') && class_exists('RulesLog', FALSE) && RulesLog::logger()->hasErrors();
1337
}
1338

    
1339
/**
1340
 * Implements hook_exit().
1341
 */
1342
function rules_exit() {
1343
  // Bail out if this is cached request and modules are not loaded.
1344
  if (!module_exists('rules') || !module_exists('user')) {
1345
    return;
1346
  }
1347
  if (rules_show_debug_output()) {
1348
    if ($log = RulesLog::logger()->render()) {
1349
      // Keep the log in the session so we can show it on the next page.
1350
      $_SESSION['rules_debug'][] = $log;
1351
    }
1352
  }
1353
  // Log the rules log to the system log if enabled.
1354
  if (variable_get('rules_debug_log', FALSE) && $log = RulesLog::logger()->render()) {
1355
    watchdog('rules', 'Rules debug information: !log', array('!log' => $log), WATCHDOG_NOTICE);
1356
  }
1357
}
1358

    
1359
/**
1360
 * Implements hook_element_info().
1361
 */
1362
function rules_element_info() {
1363
  // A duration form element for rules. Needs ui.forms.inc included.
1364
  $types['rules_duration'] = array(
1365
    '#input' => TRUE,
1366
    '#tree' => TRUE,
1367
    '#default_value' => 0,
1368
    '#value_callback' => 'rules_ui_element_duration_value',
1369
    '#process' => array('rules_ui_element_duration_process', 'ajax_process_form'),
1370
    '#after_build' => array('rules_ui_element_duration_after_build'),
1371
    '#pre_render' => array('form_pre_render_conditional_form_element'),
1372
  );
1373
  $types['rules_data_selection'] = array(
1374
    '#input' => TRUE,
1375
    '#pre_render' => array('form_pre_render_conditional_form_element'),
1376
    '#process' => array('rules_data_selection_process', 'ajax_process_form'),
1377
    '#theme' => 'rules_autocomplete',
1378
  );
1379
  return $types;
1380
}
1381

    
1382
/**
1383
 * Implements hook_modules_enabled().
1384
 */
1385
function rules_modules_enabled($modules) {
1386
  // Re-enable Rules configurations that are dirty, because they require one of
1387
  // the enabled the modules.
1388
  $query = db_select('rules_dependencies', 'rd');
1389
  $query->join('rules_config', 'rc', 'rd.id = rc.id');
1390
  $query->fields('rd', array('id'))
1391
        ->condition('rd.module', $modules, 'IN')
1392
        ->condition('rc.dirty', 1);
1393
  $ids = $query->execute()->fetchCol();
1394

    
1395
  // If there are some configurations that might work again, re-check all dirty
1396
  // configurations as others might work again too, e.g. consider a rule that is
1397
  // dirty because it requires a dirty component.
1398
  if ($ids) {
1399
    $rules_configs = rules_config_load_multiple(FALSE, array('dirty' => 1));
1400
    foreach ($rules_configs as $rules_config) {
1401
      try {
1402
        $rules_config->integrityCheck();
1403
        // If no exceptions were thrown we can set the configuration back to OK.
1404
        db_update('rules_config')
1405
          ->fields(array('dirty' => 0))
1406
          ->condition('id', $rules_config->id)
1407
          ->execute();
1408
        if ($rules_config->active) {
1409
          drupal_set_message(t('All dependencies for the Rules configuration %config are met again, so it has been re-activated.', array('%config' => $rules_config->label())));
1410
        }
1411
      }
1412
      catch (RulesIntegrityException $e) {
1413
        // The rule is still dirty, so do nothing.
1414
      }
1415
    }
1416
  }
1417
  rules_clear_cache();
1418
}
1419

    
1420
/**
1421
 * Implements hook_modules_disabled().
1422
 */
1423
function rules_modules_disabled($modules) {
1424
  // Disable Rules configurations that depend on one of the disabled modules.
1425
  $query = db_select('rules_dependencies', 'rd');
1426
  $query->join('rules_config', 'rc', 'rd.id = rc.id');
1427
  $query->fields('rd', array('id'))
1428
        ->distinct()
1429
        ->condition('rd.module', $modules, 'IN')
1430
        ->condition('rc.dirty', 0);
1431
  $ids = $query->execute()->fetchCol();
1432

    
1433
  if (!empty($ids)) {
1434
    db_update('rules_config')
1435
      ->fields(array('dirty' => 1))
1436
      ->condition('id', $ids, 'IN')
1437
      ->execute();
1438
    // Tell the user about enabled rules that have been marked as dirty.
1439
    $count = db_select('rules_config', 'r')
1440
      ->fields('r')
1441
      ->condition('id', $ids, 'IN')
1442
      ->condition('active', 1)
1443
      ->execute()->rowCount();
1444
    if ($count > 0) {
1445
      $message = format_plural($count,
1446
        '1 Rules configuration requires some of the disabled modules to function and cannot be executed any more.',
1447
        '@count Rules configuration require some of the disabled modules to function and cannot be executed any more.'
1448
      );
1449
      drupal_set_message($message, 'warning');
1450
    }
1451
  }
1452
  rules_clear_cache();
1453
}
1454

    
1455
/**
1456
 * Access callback for dealing with Rules configurations.
1457
 *
1458
 * @see entity_access()
1459
 */
1460
function rules_config_access($op, $rules_config = NULL, $account = NULL) {
1461
  if (user_access('bypass rules access', $account)) {
1462
    return TRUE;
1463
  }
1464
  // Allow modules to grant / deny access.
1465
  $access = module_invoke_all('rules_config_access', $op, $rules_config, $account);
1466

    
1467
  // Only grant access if at least one module granted access and no one denied
1468
  // access.
1469
  if (in_array(FALSE, $access, TRUE)) {
1470
    return FALSE;
1471
  }
1472
  elseif (in_array(TRUE, $access, TRUE)) {
1473
    return TRUE;
1474
  }
1475
  return FALSE;
1476
}
1477

    
1478
/**
1479
 * Implements hook_rules_config_access().
1480
 */
1481
function rules_rules_config_access($op, $rules_config = NULL, $account = NULL) {
1482
  // Instead of returning FALSE return nothing, so others still can grant
1483
  // access.
1484
  if (!isset($rules_config) || (isset($account) && $account->uid != $GLOBALS['user']->uid)) {
1485
    return;
1486
  }
1487
  if (user_access('administer rules', $account) && ($op == 'view' || $rules_config->access())) {
1488
    return TRUE;
1489
  }
1490
}
1491

    
1492
/**
1493
 * Implements hook_menu().
1494
 */
1495
function rules_menu() {
1496
  $items['admin/config/workflow/rules/upgrade'] = array(
1497
    'title' => 'Upgrade',
1498
    'page callback' => 'drupal_get_form',
1499
    'page arguments' => array('rules_upgrade_form'),
1500
    'access arguments' => array('administer rules'),
1501
    'file' => 'includes/rules.upgrade.inc',
1502
    'file path' => drupal_get_path('module', 'rules'),
1503
    'type' => MENU_CALLBACK,
1504
  );
1505
  $items['admin/config/workflow/rules/upgrade/clear'] = array(
1506
    'title' => 'Clear',
1507
    'page callback' => 'drupal_get_form',
1508
    'page arguments' => array('rules_upgrade_confirm_clear_form'),
1509
    'access arguments' => array('administer rules'),
1510
    'file' => 'includes/rules.upgrade.inc',
1511
    'file path' => drupal_get_path('module', 'rules'),
1512
    'type' => MENU_CALLBACK,
1513
  );
1514
  $items['admin/config/workflow/rules/autocomplete_tags'] = array(
1515
    'title' => 'Rules tags autocomplete',
1516
    'page callback' => 'rules_autocomplete_tags',
1517
    'page arguments' => array(5),
1518
    'access arguments' => array('administer rules'),
1519
    'file' => 'ui/ui.forms.inc',
1520
    'type' => MENU_CALLBACK,
1521
  );
1522
  return $items;
1523
}
1524

    
1525
/**
1526
 * Helper function to keep track of external documentation pages for Rules.
1527
 *
1528
 * @param $topic
1529
 *   The topic key for used for identifying help pages.
1530
 *
1531
 * @return
1532
 *   Either a URL for the given page, or the full list of external help pages.
1533
 */
1534
function rules_external_help($topic = NULL) {
1535
  $help = array(
1536
    'rules' =>                'http://drupal.org/node/298480',
1537
    'terminology' =>          'http://drupal.org/node/1299990',
1538
    'condition-components' => 'http://drupal.org/node/1300034',
1539
    'data-selection' =>       'http://drupal.org/node/1300042',
1540
    'chained-tokens' =>       'http://drupal.org/node/1300042',
1541
    'loops' =>                'http://drupal.org/node/1300058',
1542
    'components' =>           'http://drupal.org/node/1300024',
1543
    'component-types' =>      'http://drupal.org/node/1300024',
1544
    'variables' =>            'http://drupal.org/node/1300024',
1545
    'scheduler' =>            'http://drupal.org/node/1300068',
1546
    'coding' =>               'http://drupal.org/node/878720',
1547
  );
1548

    
1549
  if (isset($topic)) {
1550
    return isset($help[$topic]) ? $help[$topic] : FALSE;
1551
  }
1552
  return $help;
1553
}
1554

    
1555
/**
1556
 * Implements hook_help().
1557
 */
1558
function rules_help($path, $arg) {
1559
  // Only enable the help if the admin module is active.
1560
  if ($path == 'admin/help#rules' && module_exists('rules_admin')) {
1561

    
1562
    $output['header'] = array(
1563
      '#markup' => t('Rules documentation is kept online. Please use the links below for more information about Rules. Feel free to contribute to improving the online documentation!'),
1564
    );
1565
    // Build a list of essential Rules help pages, formatted as a bullet list.
1566
    $link_list['rules'] = l(t('Rules introduction'), rules_external_help('rules'));
1567
    $link_list['terminology'] = l(t('Rules terminology'), rules_external_help('terminology'));
1568
    $link_list['scheduler'] = l(t('Rules Scheduler'), rules_external_help('scheduler'));
1569
    $link_list['coding'] = l(t('Coding for Rules'), rules_external_help('coding'));
1570

    
1571
    $output['topic-list'] = array(
1572
      '#markup' => theme('item_list', array('items' => $link_list)),
1573
    );
1574
    return render($output);
1575
  }
1576
}
1577

    
1578
/**
1579
 * Implements hook_token_info().
1580
 */
1581
function rules_token_info() {
1582
  $cache = rules_get_cache();
1583
  $data_info = $cache['data_info'];
1584

    
1585
  $types = array('text', 'integer', 'uri', 'token', 'decimal', 'date', 'duration');
1586

    
1587
  foreach ($types as $type) {
1588
    $token_type = $data_info[$type]['token type'];
1589

    
1590
    $token_info['types'][$token_type] = array(
1591
      'name' => $data_info[$type]['label'],
1592
      'description' => t('Tokens related to %label Rules variables.', array('%label' => $data_info[$type]['label'])),
1593
      'needs-data' => $token_type,
1594
    );
1595
    $token_info['tokens'][$token_type]['value'] = array(
1596
      'name' => t("Value"),
1597
      'description' => t('The value of the variable.'),
1598
    );
1599
  }
1600
  return $token_info;
1601
}
1602

    
1603
/**
1604
 * Implements hook_tokens().
1605
 */
1606
function rules_tokens($type, $tokens, $data, $options = array()) {
1607
  // Handle replacements of primitive variable types.
1608
  if (substr($type, 0, 6) == 'rules_' && !empty($data[$type])) {
1609
    // Leverage entity tokens token processor by passing on as struct.
1610
    $info['property info']['value'] = array(
1611
      'type' => substr($type, 6),
1612
      'label' => '',
1613
    );
1614
    // Entity tokens uses metadata wrappers as values for 'struct' types.
1615
    $wrapper = entity_metadata_wrapper('struct', array('value' => $data[$type]), $info);
1616
    return entity_token_tokens('struct', $tokens, array('struct' => $wrapper), $options);
1617
  }
1618
}
1619

    
1620
/**
1621
 * Helper function that retrieves a metadata wrapper with all properties.
1622
 *
1623
 * Note that without this helper, bundle-specific properties aren't added.
1624
 */
1625
function rules_get_entity_metadata_wrapper_all_properties(RulesAbstractPlugin $element) {
1626
  return entity_metadata_wrapper($element->settings['type'], NULL, array(
1627
    'property info alter' => 'rules_entity_metadata_wrapper_all_properties_callback',
1628
  ));
1629
}
1630

    
1631
/**
1632
 * Callback that returns a metadata wrapper with all properties.
1633
 */
1634
function rules_entity_metadata_wrapper_all_properties_callback(EntityMetadataWrapper $wrapper, $property_info) {
1635
  $info = $wrapper->info();
1636
  $properties = entity_get_all_property_info($info['type']);
1637
  $property_info['properties'] += $properties;
1638
  return $property_info;
1639
}