rules_core.eval.inc 8.6 KB

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