rules_core.eval.inc 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. <?php
  2. /**
  3. * @file
  4. * Contains rules core integration needed during evaluation.
  5. *
  6. * @addtogroup rules
  7. * @{
  8. */
  9. /**
  10. * Action and condition callback: Invokes a rules component.
  11. *
  12. * We do not use the execute() method, but handle executing ourself. That way
  13. * we can utilize the existing state for saving passed variables.
  14. */
  15. function rules_element_invoke_component($arguments, RulesPlugin $element) {
  16. $info = $element->info();
  17. $state = $arguments['state'];
  18. $wrapped_args = $state->currentArguments;
  19. if ($component = rules_get_cache('comp_' . $info['#config_name'])) {
  20. $replacements = array('%label' => $component->label(), '@plugin' => $component->plugin());
  21. // Handle recursion prevention.
  22. if ($state->isBlocked($component)) {
  23. return rules_log('Not evaluating @plugin %label to prevent recursion.', $replacements, RulesLog::INFO, $component);
  24. }
  25. $state->block($component);
  26. rules_log('Evaluating @plugin %label.', $replacements, RulesLog::INFO, $component, TRUE);
  27. module_invoke_all('rules_config_execute', $component);
  28. // Manually create a new evaluation state and evaluate the component.
  29. $args = array_intersect_key($wrapped_args, $component->parameterInfo());
  30. $new_state = $component->setUpState($wrapped_args);
  31. $return = $component->evaluate($new_state);
  32. // Care for the right return value in case we have to provide vars.
  33. if ($component instanceof RulesActionInterface && !empty($info['provides'])) {
  34. $return = array();
  35. foreach ($info['provides'] as $var => $var_info) {
  36. $return[$var] = $new_state->get($var);
  37. }
  38. }
  39. // Now merge the info about to be saved variables in the parent state.
  40. $state->mergeSaveVariables($new_state, $component, $element->settings);
  41. $state->unblock($component);
  42. // Cleanup the state, what saves not mergable variables now.
  43. $new_state->cleanup();
  44. rules_log('Finished evaluation of @plugin %label.', $replacements, RulesLog::INFO, $component, FALSE);
  45. return $return;
  46. }
  47. else {
  48. throw new RulesEvaluationException('Unable to get the component %name', array('%name' => $info['#config_name']), $element, RulesLog::ERROR);
  49. }
  50. }
  51. /**
  52. * A class implementing a rules input evaluator processing date input. This is
  53. * needed to treat relative date inputs for strtotime right, consider "now".
  54. */
  55. class RulesDateInputEvaluator extends RulesDataInputEvaluator {
  56. const DATE_REGEX_LOOSE = '/^(\d{4})-?(\d{2})-?(\d{2})([T\s]?(\d{2}):?(\d{2}):?(\d{2})?)?$/';
  57. public function prepare($text, $var_info) {
  58. if (is_numeric($text)) {
  59. // Let rules skip this input evaluators in case it's already a timestamp.
  60. $this->setting = NULL;
  61. }
  62. }
  63. public function evaluate($text, $options, RulesState $state) {
  64. return self::gmstrtotime($text);
  65. }
  66. /**
  67. * Convert a time string to a GMT (UTC) unix timestamp.
  68. */
  69. public static function gmstrtotime($date) {
  70. // Pass the current timestamp in UTC to ensure the retrieved time is UTC.
  71. return strtotime($date, time());
  72. }
  73. /**
  74. * Determine whether the given date string specifies a fixed date.
  75. */
  76. public static function isFixedDateString($date) {
  77. return is_string($date) && preg_match(self::DATE_REGEX_LOOSE, $date);
  78. }
  79. }
  80. /**
  81. * A class implementing a rules input evaluator processing URI inputs to make
  82. * sure URIs are absolute and path aliases get applied.
  83. */
  84. class RulesURIInputEvaluator extends RulesDataInputEvaluator {
  85. public function prepare($uri, $var_info) {
  86. if (!isset($this->processor) && valid_url($uri, TRUE)) {
  87. // Only process if another evaluator is used or the url is not absolute.
  88. $this->setting = NULL;
  89. }
  90. }
  91. public function evaluate($uri, $options, RulesState $state) {
  92. if (!url_is_external($uri)) {
  93. // Extract the path and build the URL using the url() function, so URL
  94. // aliases are applied and query parameters and fragments get handled.
  95. $url = drupal_parse_url($uri);
  96. $url_options = array('absolute' => TRUE);
  97. $url_options['query'] = $url['query'];
  98. $url_options['fragment'] = $url['fragment'];
  99. return url($url['path'], $url_options);
  100. }
  101. elseif (valid_url($uri)) {
  102. return $uri;
  103. }
  104. throw new RulesEvaluationException('Input evaluation generated an invalid URI.', array(), NULL, RulesLog::WARN);
  105. }
  106. }
  107. /**
  108. * A data processor for applying date offsets.
  109. */
  110. class RulesDateOffsetProcessor extends RulesDataProcessor {
  111. protected static function form($settings, $var_info) {
  112. $settings += array('value' => '');
  113. $form = array(
  114. '#type' => 'fieldset',
  115. '#title' => t('Add offset'),
  116. '#collapsible' => TRUE,
  117. '#collapsed' => empty($settings['value']),
  118. '#description' => t('Add an offset to the selected date.'),
  119. );
  120. $form['value'] = array(
  121. '#type' => 'rules_duration',
  122. '#title' => t('Offset'),
  123. '#description' => t('Note that you can also specify negative numbers.'),
  124. '#default_value' => $settings['value'],
  125. '#weight' => 5,
  126. );
  127. return $form;
  128. }
  129. public function process($value, $info, RulesState $state, RulesPlugin $element) {
  130. $value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element) : $value;
  131. return RulesDateOffsetProcessor::applyOffset($value, $this->setting['value']);
  132. }
  133. /**
  134. * Intelligently applies the given date offset in seconds.
  135. *
  136. * Intelligently apply duration values > 1 day, i.e. convert the duration
  137. * to its biggest possible unit (months, days) and apply it to the date with
  138. * the given unit. That's necessary as the number of days in a month
  139. * differs, as well as the number of hours for a day (on DST changes).
  140. */
  141. public static function applyOffset($timestamp, $offset) {
  142. if (abs($offset) >= 86400) {
  143. // Get the days out of the seconds.
  144. $days = intval($offset / 86400);
  145. $sec = $offset % 86400;
  146. // Get the months out of the number of days.
  147. $months = intval($days / 30);
  148. $days = $days % 30;
  149. // Apply the offset using the DateTime::modify and convert it back to a
  150. // timestamp.
  151. $date = date_create("@$timestamp");
  152. $date->modify("$months months $days days $sec seconds");
  153. return $date->format('U');
  154. }
  155. else {
  156. return $timestamp + $offset;
  157. }
  158. }
  159. }
  160. /**
  161. * A data processor for applying numerical offsets.
  162. */
  163. class RulesNumericOffsetProcessor extends RulesDataProcessor {
  164. protected static function form($settings, $var_info) {
  165. $settings += array('value' => '');
  166. $form = array(
  167. '#type' => 'fieldset',
  168. '#title' => t('Add offset'),
  169. '#collapsible' => TRUE,
  170. '#collapsed' => empty($settings['value']),
  171. '#description' => t('Add an offset to the selected number. E.g. an offset of "1" adds 1 to the number before it is passed on as argument.'),
  172. );
  173. $form['value'] = array(
  174. '#type' => 'textfield',
  175. '#title' => t('Offset'),
  176. '#description' => t('Note that you can also specify negative numbers.'),
  177. '#default_value' => $settings['value'],
  178. '#element_validate' => array('rules_ui_element_integer_validate'),
  179. '#weight' => 5,
  180. );
  181. return $form;
  182. }
  183. public function process($value, $info, RulesState $state, RulesPlugin $element) {
  184. $value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element) : $value;
  185. return $value + $this->setting['value'];
  186. }
  187. }
  188. /**
  189. * A custom wrapper class for vocabularies that is capable of loading vocabularies by machine name.
  190. */
  191. class RulesTaxonomyVocabularyWrapper extends EntityDrupalWrapper {
  192. /**
  193. * Overridden to support identifying vocabularies by machine names.
  194. */
  195. protected function setEntity($data) {
  196. if (isset($data) && $data !== FALSE && !is_object($data) && !is_numeric($data)) {
  197. // The vocabulary name has been passed.
  198. parent::setEntity(taxonomy_vocabulary_machine_name_load($data));
  199. }
  200. else {
  201. parent::setEntity($data);
  202. }
  203. }
  204. /**
  205. * Overriden to permit machine names as values.
  206. */
  207. public function validate($value) {
  208. if (isset($value) && is_string($value)) {
  209. return TRUE;
  210. }
  211. return parent::validate($value);
  212. }
  213. }