ui.forms.inc 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. <?php
  2. /**
  3. * @file Rules UI forms
  4. */
  5. /**
  6. * Ajax callback for reloading the whole form.
  7. */
  8. function rules_ui_form_ajax_reload_form($form, $form_state) {
  9. return $form;
  10. }
  11. /**
  12. * Defines #ajax properties.
  13. */
  14. function rules_ui_form_default_ajax($effect = 'slide') {
  15. return array(
  16. 'callback' => 'rules_ui_form_ajax_reload_form',
  17. 'wrapper' => 'rules-form-wrapper',
  18. 'effect' => $effect,
  19. 'speed' => 'fast',
  20. );
  21. }
  22. /**
  23. * Submit handler for switching the parameter input mode.
  24. */
  25. function rules_ui_parameter_replace_submit($form, &$form_state) {
  26. if (isset($form_state['triggering_element'])) {
  27. $name = $form_state['triggering_element']['#parameter'];
  28. $form_state['parameter_mode'][$name] = $form_state['parameter_mode'][$name] == 'selector' ? 'input' : 'selector';
  29. }
  30. $form_state['rebuild'] = TRUE;
  31. }
  32. /**
  33. * General form submit handler, that rebuilds the form
  34. */
  35. function rules_form_submit_rebuild($form, &$form_state) {
  36. $form_state['rebuild'] = TRUE;
  37. }
  38. /**
  39. * Edit a rules configuration.
  40. */
  41. function rules_ui_form_edit_rules_config($form, &$form_state, $rules_config, $base_path) {
  42. RulesPluginUI::$basePath = $base_path;
  43. $form_state += array('rules_element' => $rules_config);
  44. // Add the rule configuration's form.
  45. $rules_config->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE));
  46. $form['#validate'] = array('rules_ui_form_rules_config_validate');
  47. return $form;
  48. }
  49. /**
  50. * General rules configuration form validation callback. Also populates the
  51. * rules configuration with the form values.
  52. */
  53. function rules_ui_form_rules_config_validate($form, &$form_state) {
  54. $form_state['rules_element']->form_validate($form, $form_state);
  55. }
  56. /**
  57. * Edit a rules configuration form submit callback.
  58. */
  59. function rules_ui_form_edit_rules_config_submit($form, &$form_state) {
  60. $form_state['rules_element']->form_submit($form, $form_state);
  61. drupal_set_message(t('Your changes have been saved.'));
  62. if (empty($form_state['redirect'])) {
  63. $form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['rules_element']);
  64. }
  65. }
  66. /**
  67. * Clone a rules configuration form.
  68. */
  69. function rules_ui_form_clone_rules_config($form, &$form_state, $rules_config, $base_path) {
  70. RulesPluginUI::$basePath = $base_path;
  71. $rules_config = clone $rules_config;
  72. $rules_config->module = 'rules';
  73. $rules_config->id = NULL;
  74. $rules_config->name = '';
  75. $rules_config->label .= ' (' . t('cloned') . ')';
  76. $rules_config->status = ENTITY_CUSTOM;
  77. $form['#validate'][] = 'rules_ui_form_rules_config_validate';
  78. $form['#submit'][] = 'rules_ui_form_edit_rules_config_submit';
  79. $form_state += array('rules_element' => $rules_config, 'op' => 'clone');
  80. // Add the rule configuration's form.
  81. $rules_config->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE, 'init' => TRUE));
  82. // Open the settings fieldset so altering the name is easier.
  83. $form['settings']['#collapsed'] = FALSE;
  84. return $form;
  85. }
  86. /**
  87. * A simple form just showing a textarea with the export.
  88. */
  89. function rules_ui_form_export_rules_config($form, &$form_state, $rules_config, $base_path) {
  90. $form['export'] = array(
  91. '#type' => 'textarea',
  92. '#title' => t('Export'),
  93. '#description' => t('For importing copy the content of the text area and paste it into the import page.'),
  94. '#rows' => 25,
  95. '#default_value' => $rules_config->export(),
  96. );
  97. return $form;
  98. }
  99. /**
  100. * Configuration form to directly execute a rules configuration.
  101. */
  102. function rules_ui_form_execute_rules_config($form, &$form_state, $rules_config, $base_path) {
  103. // Only components can be executed.
  104. if (!($rules_config instanceof RulesTriggerableInterface)) {
  105. RulesPluginUI::$basePath = $base_path;
  106. // Create either the appropriate action or condition element.
  107. $element = rules_plugin_factory($rules_config instanceof RulesActionInterface ? 'action' : 'condition', 'component_' . $rules_config->name);
  108. $form['exec_help'] = array(
  109. '#prefix' => '<p>',
  110. '#markup' => t('This form allows you to manually trigger the execution of the @plugin "%label". If this component requires any parameters, input the suiting execution arguments below.', array('@plugin' => $rules_config->plugin(), '%label' => $rules_config->label())),
  111. '#suffix' => '</p>',
  112. );
  113. $element->form($form, $form_state);
  114. // For conditions hide the option to negate them.
  115. if (isset($form['negate'])) {
  116. $form['negate']['#access'] = FALSE;
  117. }
  118. $form['submit'] = array(
  119. '#type' => 'submit',
  120. '#value' => t('Execute'),
  121. '#weight' => 20,
  122. );
  123. // Re-use the validation callback, which will also populate the action with
  124. // the configuration settings in the form.
  125. $form['#validate'] = array('rules_ui_form_rules_config_validate');
  126. return $form;
  127. }
  128. drupal_not_found();
  129. exit;
  130. }
  131. /**
  132. * Submit callback for directly executing a component.
  133. */
  134. function rules_ui_form_execute_rules_config_submit($form, &$form_state) {
  135. $element = $form_state['rules_element'];
  136. $result = $element->execute();
  137. if ($element instanceof RulesActionInterface) {
  138. drupal_set_message(t('Component %label has been executed.', array('%label' => $element->label())));
  139. }
  140. else {
  141. drupal_set_message(t('Component %label evaluated to %result.', array('%label' => $element->label(), '%result' => $result ? 'true' : 'false')));
  142. }
  143. }
  144. /**
  145. * Gets the confirmation question for valid operations, or else FALSE.
  146. */
  147. function rules_ui_confirm_operations($op, $rules_config) {
  148. $vars = array('%plugin' => $rules_config->plugin(), '%label' => $rules_config->label());
  149. switch ($op) {
  150. case 'enable':
  151. return array(t('Are you sure you want to enable the %plugin %label?', $vars), '');
  152. case 'disable':
  153. return array(t('Are you sure you want to disable the %plugin %label?', $vars), '');
  154. case 'revert':
  155. return array(t('Are you sure you want to revert the %plugin %label?', $vars), t('This action cannot be undone.'));
  156. case 'delete':
  157. return array(t('Are you sure you want to delete the %plugin %label?', $vars), t('This action cannot be undone.'));
  158. default:
  159. return FALSE;
  160. }
  161. }
  162. /**
  163. * Confirmation form for applying the operation to the config.
  164. */
  165. function rules_ui_form_rules_config_confirm_op($form, &$form_state, $rules_config, $op, $base_path) {
  166. if (list($confirm_question, $description) = rules_ui_confirm_operations($op, $rules_config)) {
  167. RulesPluginUI::$basePath = $base_path;
  168. $form_state += array('rules_config' => $rules_config, 'op' => $op);
  169. return confirm_form($form, $confirm_question, $base_path, $description, t('Confirm'), t('Cancel'));
  170. }
  171. else {
  172. drupal_not_found();
  173. exit;
  174. }
  175. }
  176. /**
  177. * Applies the operation and returns the message to show to the user. Also the
  178. * operation is logged to the watchdog. Note that the string is defined two
  179. * times so that the translation extractor can find it.
  180. */
  181. function rules_ui_confirm_operation_apply($op, $rules_config) {
  182. $vars = array('%plugin' => $rules_config->plugin(), '%label' => $rules_config->label());
  183. $edit_link = l(t('edit'), RulesPluginUI::path($rules_config->name));
  184. switch ($op) {
  185. case 'enable':
  186. $rules_config->active = TRUE;
  187. $rules_config->save();
  188. watchdog('rules', 'Enabled %plugin %label.', $vars, WATCHDOG_NOTICE, $edit_link);
  189. return t('Enabled %plugin %label.', $vars);
  190. case 'disable':
  191. $rules_config->active = FALSE;
  192. $rules_config->save();
  193. watchdog('rules', 'Disabled %plugin %label.', $vars, WATCHDOG_NOTICE, $edit_link);
  194. return t('Disabled %plugin %label.', $vars);
  195. case 'revert':
  196. $rules_config->delete();
  197. watchdog('rules', 'Reverted %plugin %label to the defaults.', $vars, WATCHDOG_NOTICE, $edit_link);
  198. return t('Reverted %plugin %label to the defaults.', $vars);
  199. case 'delete':
  200. $rules_config->delete();
  201. watchdog('rules', 'Deleted %plugin %label.', $vars);
  202. return t('Deleted %plugin %label.', $vars);
  203. }
  204. }
  205. /**
  206. * Rule config deletion form submit callback.
  207. */
  208. function rules_ui_form_rules_config_confirm_op_submit($form, &$form_state) {
  209. if ($form_state['values']['confirm']) {
  210. $msg = rules_ui_confirm_operation_apply($form_state['op'], $form_state['rules_config']);
  211. drupal_set_message($msg);
  212. }
  213. }
  214. /**
  215. * Add a new element a rules configuration.
  216. */
  217. function rules_ui_add_element($form, &$form_state, $rules_config, $plugin_name, RulesContainerPlugin $parent, $base_path) {
  218. $cache = rules_get_cache();
  219. if (!isset($cache['plugin_info'][$plugin_name]['class'])) {
  220. drupal_not_found();
  221. exit;
  222. }
  223. RulesPluginUI::$basePath = $base_path;
  224. $plugin_is_abstract = in_array('RulesAbstractPlugin', class_parents($cache['plugin_info'][$plugin_name]['class']));
  225. // In the first step create the element and in the second step show its edit
  226. // form.
  227. if ($plugin_is_abstract && !isset($form_state['rules_element'])) {
  228. RulesPluginUI::formDefaults($form, $form_state);
  229. $form_state += array('parent_element' => $parent, 'plugin' => $plugin_name);
  230. $form['element_name'] = array(
  231. '#type' => 'select',
  232. '#title' => t('Select the %element to add', array('%element' => $plugin_name)),
  233. '#options' => RulesPluginUI::getOptions($plugin_name),
  234. '#ajax' => rules_ui_form_default_ajax() + array(
  235. 'trigger_as' => array('name' => 'continue'),
  236. ),
  237. );
  238. $form['continue'] = array(
  239. '#type' => 'submit',
  240. '#name' => 'continue',
  241. '#value' => t('Continue'),
  242. '#ajax' => rules_ui_form_default_ajax(),
  243. );
  244. }
  245. elseif (!$plugin_is_abstract) {
  246. // Create the initial, empty element.
  247. $element = rules_plugin_factory($plugin_name);
  248. // Always add the new element at the bottom, thus set an appropriate weight.
  249. $iterator = $parent->getIterator();
  250. if ($sibling = end($iterator)) {
  251. $element->weight = $sibling->weight + 1;
  252. }
  253. $element->setParent($parent);
  254. $form_state['rules_element'] = $element;
  255. }
  256. if (isset($form_state['rules_element'])) {
  257. $form_state['rules_element']->form($form, $form_state, array('button' => TRUE, 'init' => TRUE));
  258. $form['#validate'][] = 'rules_ui_edit_element_validate';
  259. $form['#submit'][] = 'rules_ui_edit_element_submit';
  260. }
  261. return $form;
  262. }
  263. /**
  264. * Add element submit callback.
  265. * Used for "abstract plugins" to create the initial element object with the
  266. * given implemenation name and rebuild the form.
  267. */
  268. function rules_ui_add_element_submit($form, &$form_state) {
  269. $element = rules_plugin_factory($form_state['plugin'], $form_state['values']['element_name']);
  270. // Always add the new element at the bottom, thus set an appropriate weight.
  271. $iterator = $form_state['parent_element']->getIterator();
  272. if ($sibling = end($iterator)) {
  273. $element->weight = $sibling->weight + 1;
  274. }
  275. // Clear the element settings so they won't be processed on serialization as
  276. // there is nothing to be processed yet.
  277. $element->settings = array();
  278. $element->setParent($form_state['parent_element']);
  279. $form_state['rules_element'] = $element;
  280. $form_state['rebuild'] = TRUE;
  281. }
  282. /**
  283. * Delete elements.
  284. */
  285. function rules_ui_delete_element($form, &$form_state, $rules_config, $rules_element, $base_path) {
  286. RulesPluginUI::$basePath = $base_path;
  287. if (empty($form_state['rules_config'])) {
  288. // Before modifying the rules config we have to clone it, so any
  289. // modifications won't appear in the static cache of the loading controller.
  290. $rules_config = clone $rules_config;
  291. // Also get the element from the cloned config.
  292. $rules_element = $rules_config->elementMap()->lookup($rules_element->elementId());
  293. $form_state['rules_config'] = $rules_config;
  294. $form_state['rules_element'] = $rules_element;
  295. $form_state['element_parent'] = $rules_element->parentElement();
  296. }
  297. // Try deleting the element and warn the user if something breaks, but
  298. // save the parent for determining the right redirect target on submit.
  299. $removed_plugin = $form_state['rules_element']->plugin();
  300. $rules_element->delete();
  301. if (empty($rules_config->dirty) && empty($form_state['input'])) {
  302. try {
  303. $rules_config->integrityCheck();
  304. }
  305. catch (RulesIntegrityException $e) {
  306. $args = array(
  307. '@plugin' => $e->element->plugin(),
  308. '%label' => $e->element->label(),
  309. '@removed-plugin' => $removed_plugin,
  310. '!url' => url(RulesPluginUI::path($form_state['rules_config']->name, 'edit', $e->element)),
  311. );
  312. drupal_set_message(t('Deleting this @removed-plugin would break your configuration as some of its provided variables are utilized by the @plugin <a href="!url">%label</a>.', $args), 'warning');
  313. }
  314. }
  315. $confirm_question = t('Are you sure you want to delete the %element_plugin %element_name?', array('%element_plugin' => $rules_element->plugin(), '%element_name' => $rules_element->label(), '%plugin' => $rules_config->plugin(), '%label' => $rules_config->label()));
  316. return confirm_form($form, $confirm_question, RulesPluginUI::path($rules_config->name), t('This action cannot be undone.'), t('Delete'), t('Cancel'));
  317. }
  318. /**
  319. * Rule config deletion form submit callback.
  320. */
  321. function rules_ui_delete_element_submit($form, &$form_state) {
  322. $rules_config = $form_state['rules_config'];
  323. $rules_config->save();
  324. if (empty($form_state['redirect'])) {
  325. $form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['element_parent']);
  326. }
  327. }
  328. /**
  329. * Configure a rule element.
  330. */
  331. function rules_ui_edit_element($form, &$form_state, $rules_config, $element, $base_path) {
  332. RulesPluginUI::$basePath = $base_path;
  333. $form_state += array('rules_element' => $element);
  334. $form_state['rules_element']->form($form, $form_state, array('button' => TRUE));
  335. return $form;
  336. }
  337. /**
  338. * Validate the element configuration.
  339. */
  340. function rules_ui_edit_element_validate($form, &$form_state) {
  341. $form_state['rules_element']->form_validate($form, $form_state);
  342. }
  343. /**
  344. * Submit the element configuration.
  345. */
  346. function rules_ui_edit_element_submit($form, &$form_state) {
  347. $form_state['rules_element']->form_submit($form, $form_state);
  348. drupal_set_message(t('Your changes have been saved.'));
  349. if (empty($form_state['redirect'])) {
  350. $form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['rules_element']);
  351. }
  352. }
  353. /**
  354. * Add a new event.
  355. */
  356. function rules_ui_add_event($form, &$form_state, RulesReactionRule $rules_config, $base_path) {
  357. RulesPluginUI::$basePath = $base_path;
  358. $form_state += array('rules_config' => $rules_config);
  359. $events = array_diff_key(rules_fetch_data('event_info'), array_flip($rules_config->events()));
  360. $form['help'] = array(
  361. '#markup' => t('Select the event to add. However note that all added events need to provide all variables that should be available to your rule.'),
  362. );
  363. $form['event'] = array(
  364. '#type' => 'select',
  365. '#title' => t('React on event'),
  366. '#options' => RulesPluginUI::getOptions('event', $events),
  367. '#description' => t('Whenever the event occurs, rule evaluation is triggered.'),
  368. );
  369. $form['submit'] = array(
  370. '#type' => 'submit',
  371. '#value' => t('Add'),
  372. );
  373. $form_state['redirect'] = RulesPluginUI::path($rules_config->name);
  374. return $form;
  375. }
  376. /**
  377. * Submit callback that just adds the selected event.
  378. *
  379. * @see rules_admin_add_reaction_rule()
  380. */
  381. function rules_ui_add_event_apply($form, &$form_state) {
  382. $form_state['rules_config']->event($form_state['values']['event']);
  383. }
  384. /**
  385. * Submit the event configuration.
  386. */
  387. function rules_ui_add_event_submit($form, &$form_state) {
  388. rules_ui_add_event_apply($form, $form_state);
  389. $rules_config = $form_state['rules_config'];
  390. // Tell the user if this breaks something, but let him proceed.
  391. if (empty($rules_config->dirty)) {
  392. try {
  393. $rules_config->integrityCheck();
  394. }
  395. catch (RulesIntegrityException $e) {
  396. $warning = TRUE;
  397. drupal_set_message(t('Added the event, but it does not provide all variables utilized.'), 'warning');
  398. }
  399. }
  400. $rules_config->save();
  401. if (!isset($warning)) {
  402. $events = rules_fetch_data('event_info');
  403. $label = $events[$form_state['values']['event']]['label'];
  404. drupal_set_message(t('Added event %event.', array('%event' => $label)));
  405. }
  406. }
  407. /**
  408. * Form to remove a event from a rule.
  409. */
  410. function rules_ui_remove_event($form, &$form_state, $rules_config, $event, $base_path) {
  411. RulesPluginUI::$basePath = $base_path;
  412. $form_state += array('rules_config' => $rules_config, 'rules_event' => $event);
  413. $events = rules_fetch_data('event_info');
  414. $form_state['event_label'] = $events[$event]['label'];
  415. $confirm_question = t('Are you sure you want to remove the event?');
  416. return confirm_form($form, $confirm_question, RulesPluginUI::path($rules_config->name), t('You are about to remove the event %event.', array('%event' => $form_state['event_label'])), t('Remove'), t('Cancel'));
  417. }
  418. /**
  419. * Submit the event configuration.
  420. */
  421. function rules_ui_remove_event_submit($form, &$form_state) {
  422. $rules_config = $form_state['rules_config'];
  423. $rules_config->removeEvent($form_state['rules_event']);
  424. // Tell the user if this breaks something, but let him proceed.
  425. if (empty($rules_config->dirty)) {
  426. try {
  427. $rules_config->integrityCheck();
  428. }
  429. catch (RulesIntegrityException $e) {
  430. $warning = TRUE;
  431. drupal_set_message(t('Removed the event, but it had provided some variables which are now missing.'), 'warning');
  432. }
  433. }
  434. $rules_config->save();
  435. if (!isset($warning)) {
  436. drupal_set_message(t('Event %event has been removed.', array('%event' => $form_state['event_label'])));
  437. }
  438. $form_state['redirect'] = RulesPluginUI::path($rules_config->name);
  439. }
  440. /**
  441. * Import form for rule configurations.
  442. */
  443. function rules_ui_import_form($form, &$form_state, $base_path) {
  444. RulesPluginUI::$basePath = $base_path;
  445. RulesPluginUI::formDefaults($form, $form_state);
  446. $form['import'] = array(
  447. '#type' => 'textarea',
  448. '#title' => t('Import'),
  449. '#description' => t('Paste an exported Rules configuration here.'),
  450. '#rows' => 20,
  451. );
  452. $form['overwrite'] = array(
  453. '#title' => t('Overwrite'),
  454. '#type' => 'checkbox',
  455. '#description' => t('If checked, any existing configuration with the same identifier will be replaced by the import.'),
  456. '#default_value' => FALSE,
  457. );
  458. $form['submit'] = array(
  459. '#type' => 'submit',
  460. '#value' => t('Import'),
  461. );
  462. return $form;
  463. }
  464. /**
  465. * Validation callback for the import form.
  466. */
  467. function rules_ui_import_form_validate($form, &$form_state) {
  468. if ($rules_config = rules_import($form_state['values']['import'], $error_msg)) {
  469. // Store the successfully imported entity in $form_state.
  470. $form_state['rules_config'] = $rules_config;
  471. if (!$form_state['values']['overwrite']) {
  472. // Check for existing entities with the same identifier.
  473. if (rules_config_load($rules_config->name)) {
  474. $vars = array('@entity' => t('Rules configuration'), '%label' => $rules_config->label());
  475. form_set_error('import', t('Import of @entity %label failed, a @entity with the same machine name already exists. Check the overwrite option to replace it.', $vars));
  476. }
  477. }
  478. try {
  479. $rules_config->integrityCheck();
  480. }
  481. catch (RulesIntegrityException $e) {
  482. form_set_error('import', t('Integrity check for the imported configuration failed. Error message: %message.', array('%message' => $e->getMessage())));
  483. }
  484. if (!user_access('bypass rules access') && !$rules_config->access()) {
  485. form_set_error('import', t('You have insufficient access permissions for importing this Rules configuration.'));
  486. }
  487. }
  488. else {
  489. form_set_error('import', t('Import failed.'));
  490. if ($error_msg) {
  491. drupal_set_message($error_msg, 'error');
  492. }
  493. }
  494. }
  495. /**
  496. * Submit callback for the import form.
  497. */
  498. function rules_ui_import_form_submit($form, &$form_state) {
  499. $rules_config = $form_state['rules_config'];
  500. if ($existing_config = rules_config_load($rules_config->name)) {
  501. // Copy DB id and remove the new indicator to overwrite the existing record.
  502. $rules_config->id = $existing_config->id;
  503. unset($rules_config->is_new);
  504. }
  505. $rules_config->save();
  506. $vars = array('@entity' => t('Rules configuration'), '%label' => $rules_config->label());
  507. watchdog('rules_config', 'Imported @entity %label.', $vars);
  508. drupal_set_message(t('Imported @entity %label.', $vars));
  509. $form_state['redirect'] = RulesPluginUI::$basePath;
  510. }
  511. /**
  512. * FAPI process callback for the data selection widget.
  513. * This finalises the auto completion callback path by appending the form build
  514. * id.
  515. */
  516. function rules_data_selection_process($element, &$form_state, $form) {
  517. $element['#autocomplete_path'] .= '/' . $form['#build_id'];
  518. $form_state['cache'] = TRUE;
  519. return $element;
  520. }
  521. /**
  522. * Autocomplete data selection results.
  523. */
  524. function rules_ui_form_data_selection_auto_completion($parameter, $form_build_id, $string = '') {
  525. // Get the form and its state from the cache to get the currently edited
  526. // or created element.
  527. $form_state = form_state_defaults();
  528. $form = form_get_cache($form_build_id, $form_state);
  529. if (!isset($form_state['rules_element'])) {
  530. return;
  531. }
  532. $element = $form_state['rules_element'];
  533. $params = $element->pluginParameterInfo();
  534. $matches = array();
  535. if (isset($params[$parameter])) {
  536. $parts = explode(':', $string);
  537. // Remove the last part as it might be unfinished.
  538. $last_part = array_pop($parts);
  539. $selector = implode(':', $parts);
  540. // Start with the partly given selector or from scratch.
  541. $result = array();
  542. if ($selector && $wrapper = $element->applyDataSelector($selector)) {
  543. $result = RulesData::matchingDataSelector($wrapper, $params[$parameter], $selector . ':', 0);
  544. }
  545. elseif (!$selector) {
  546. $result = RulesData::matchingDataSelector($element->availableVariables(), $params[$parameter], '', 0);
  547. }
  548. foreach ($result as $selector => $info) {
  549. // If we have an uncomplete last part, take it into account now.
  550. $attributes = array();
  551. if (!$last_part || strpos($selector, $string) === 0) {
  552. $attributes['class'][] = 'rules-dsac-item';
  553. $attributes['title'] = isset($info['description']) ? strip_tags($info['description']) : '';
  554. if ($selector[strlen($selector) - 1] == ':') {
  555. $attributes['class'][] = 'rules-dsac-group';
  556. $text = check_plain($selector) . '... (' . check_plain($info['label']) . ')';
  557. }
  558. else {
  559. $text = check_plain($selector) . ' (' . check_plain($info['label']) . ')';
  560. }
  561. $matches[$selector] = "<div" . drupal_attributes($attributes) . ">$text</div>";
  562. }
  563. }
  564. }
  565. drupal_json_output($matches);
  566. }
  567. /**
  568. * FAPI validation of an integer element. Copy of the private function
  569. * _element_validate_integer().
  570. */
  571. function rules_ui_element_integer_validate($element, &$form_state) {;
  572. $value = $element['#value'];
  573. if (isset($value) && $value !== '' && (!is_numeric($value) || intval($value) != $value)) {
  574. form_error($element, t('%name must be an integer value.', array('%name' => isset($element['#title']) ? $element['#title'] : t('Element'))));
  575. }
  576. }
  577. /**
  578. * FAPI validation of a decimal element. Improved version of the private
  579. * function _element_validate_number().
  580. */
  581. function rules_ui_element_decimal_validate($element, &$form_state) {
  582. // Substitute the decimal separator ",".
  583. $value = strtr($element['#value'], ',', '.');
  584. if ($value != '' && !is_numeric($value)) {
  585. form_error($element, t('%name must be a number.', array('%name' => $element['#title'])));
  586. }
  587. elseif ($value != $element['#value']) {
  588. form_set_value($element, $value, $form_state);
  589. }
  590. }
  591. /**
  592. * FAPI validation of a date element. Makes sure the specified date format is
  593. * correct and converts date values specifiy a fixed (= non relative) date to
  594. * a timestamp. Relative dates are handled by the date input evaluator.
  595. */
  596. function rules_ui_element_date_validate($element, &$form_state) {
  597. $value = $element['#value'];
  598. if ($value == '' || (is_numeric($value) && intval($value) == $value)) {
  599. // The value is a timestamp.
  600. return;
  601. }
  602. elseif (is_string($value) && RulesDateInputEvaluator::gmstrtotime($value) === FALSE) {
  603. form_error($element, t('Wrong date format. Specify the date in the format %format.', array('%format' => gmdate('Y-m-d H:i:s', time() + 86400))));
  604. }
  605. elseif (is_string($value) && RulesDateInputEvaluator::isFixedDateString($value)) {
  606. // As the date string specifies a fixed format, we can convert it now.
  607. $value = RulesDateInputEvaluator::gmstrtotime($value);
  608. form_set_value($element, $value, $form_state);
  609. }
  610. }
  611. /**
  612. * FAPI process callback for the duration element type.
  613. */
  614. function rules_ui_element_duration_process($element, &$form_state) {
  615. $element['value'] = array(
  616. '#type' => 'textfield',
  617. '#size' => 8,
  618. '#element_validate' => array('rules_ui_element_integer_validate'),
  619. '#default_value' => $element['#default_value'],
  620. '#required' => !empty($element['#required']),
  621. );
  622. $element['multiplier'] = array(
  623. '#type' => 'select',
  624. '#options' => rules_ui_element_duration_multipliers(),
  625. '#default_value' => 1,
  626. );
  627. // Put the child elements in a container-inline div.
  628. $element['value']['#prefix'] = '<div class="rules-duration container-inline">';
  629. $element['multiplier']['#suffix'] = '</div>';
  630. // Set an appropriate multiplier.
  631. if (!empty($element['value']['#default_value'])) {
  632. foreach (array_keys(rules_ui_element_duration_multipliers()) as $m) {
  633. if ($element['value']['#default_value'] % $m == 0) {
  634. $element['multiplier']['#default_value'] = $m;
  635. }
  636. }
  637. // Divide value by the multiplier, so the display is correct.
  638. $element['value']['#default_value'] /= $element['multiplier']['#default_value'];
  639. }
  640. return $element;
  641. }
  642. /**
  643. * Defines possible duration multiplier.
  644. */
  645. function rules_ui_element_duration_multipliers() {
  646. return array(
  647. 1 => t('seconds'),
  648. 60 => t('minutes'),
  649. 3600 => t('hours'),
  650. // Just use approximate numbers for days (might last 23h on DST change),
  651. // months and years.
  652. 86400 => t('days'),
  653. 86400 * 30 => t('months'),
  654. 86400 * 30 * 12 => t('years'),
  655. );
  656. }
  657. /**
  658. * Helper function to determine the value for a rules duration form
  659. * element.
  660. */
  661. function rules_ui_element_duration_value($element, $input = FALSE) {
  662. // This runs before child elements are processed, so we cannot calculate the
  663. // value here. But we have to make sure the value is an array, so the form
  664. // API is able to process the children to set their values in the array. Thus
  665. // once the form API has finished processing the element, the value is an
  666. // array containing the child element values. Then finally the after build
  667. // callback converts it back to the numeric value and sets that.
  668. return array();
  669. }
  670. /**
  671. * FAPI after build callback for the duration parameter type form.
  672. * Fixes up the form value by applying the multiplier.
  673. */
  674. function rules_ui_element_duration_after_build($element, &$form_state) {
  675. if ($element['value']['#value'] !== '') {
  676. $element['#value'] = $element['value']['#value'] * $element['multiplier']['#value'];
  677. form_set_value($element, $element['#value'], $form_state);
  678. }
  679. else {
  680. $element['#value'] = NULL;
  681. form_set_value($element, NULL, $form_state);
  682. }
  683. return $element;
  684. }
  685. /**
  686. * FAPI after build callback to ensure empty form elements result in no value.
  687. */
  688. function rules_ui_element_fix_empty_after_build($element, &$form_state) {
  689. if (isset($element['#value']) && $element['#value'] === '') {
  690. $element['#value'] = NULL;
  691. form_set_value($element, NULL, $form_state);
  692. }
  693. // Work-a-round for the text_format element.
  694. elseif ($element['#type'] == 'text_format' && !isset($element['value']['#value'])) {
  695. form_set_value($element, NULL, $form_state);
  696. }
  697. return $element;
  698. }
  699. /**
  700. * FAPI after build callback for specifying a list of values.
  701. *
  702. * Turns the textual value in an array by splitting the text in chunks using the
  703. * delimiter set at $element['#delimiter'].
  704. */
  705. function rules_ui_list_textarea_after_build($element, &$form_state) {
  706. $element['#value'] = $element['#value'] ? explode($element['#delimiter'], $element['#value']) : array();
  707. $element['#value'] = array_map('trim', $element['#value']);
  708. form_set_value($element, $element['#value'], $form_state);
  709. return $element;
  710. }
  711. /**
  712. * FAPI pre render callback. Turns the value back to a string for rendering.
  713. *
  714. * @see rules_ui_list_textarea_after_build()
  715. */
  716. function rules_ui_list_textarea_pre_render($element) {
  717. $element['#value'] = implode($element['#delimiter'], $element['#value']);
  718. return $element;
  719. }
  720. /**
  721. * FAPI callback to validate a list of integers.
  722. */
  723. function rules_ui_element_integer_list_validate($element, &$form_state) {
  724. foreach ($element['#value'] as $value) {
  725. if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) {
  726. form_error($element, t('Each value must be an integer.'));
  727. }
  728. }
  729. }
  730. /**
  731. * FAPI callback to validate a token.
  732. */
  733. function rules_ui_element_token_validate($element) {
  734. $value = $element['#value'];
  735. if (isset($value) && $value !== '' && !entity_property_verify_data_type($value, 'token')) {
  736. form_error($element, t('%name may only contain lowercase letters, numbers, and underscores and has to start with a letter.', array('%name' => isset($element['#title']) ? $element['#title'] : t('Element'))));
  737. }
  738. }
  739. /**
  740. * FAPI callback to validate a list of tokens.
  741. */
  742. function rules_ui_element_token_list_validate($element, &$form_state) {
  743. foreach ($element['#value'] as $value) {
  744. if ($value !== '' && !entity_property_verify_data_type($value, 'token')) {
  745. form_error($element, t('Each value may only contain lowercase letters, numbers, and underscores and has to start with a letter.'));
  746. }
  747. }
  748. }
  749. /**
  750. * FAPI callback to validate a machine readable name.
  751. */
  752. function rules_ui_element_machine_name_validate($element, &$form_state) {
  753. if ($element['#value'] && !preg_match('!^[a-z0-9_]+$!', $element['#value'])) {
  754. form_error($element, t('Machine-readable names must contain only lowercase letters, numbers, and underscores.'));
  755. }
  756. }
  757. /**
  758. * FAPI callback to validate the form for editing variable info.
  759. * @see RulesPluginUI::getVariableForm()
  760. */
  761. function rules_ui_element_variable_form_validate($elements, &$form_state) {
  762. $names = array();
  763. foreach (element_children($elements['items']) as $item_key) {
  764. $element = &$elements['items'][$item_key];
  765. if ($element['name']['#value'] || $element['type']['#value'] || $element['label']['#value']) {
  766. foreach (array('name' => t('Machine name'), 'label' => t('Label'), 'type' => t('Data type')) as $key => $title) {
  767. if (!$element[$key]['#value']) {
  768. form_error($element[$key], t('!name field is required.', array('!name' => $title)));
  769. }
  770. }
  771. if (isset($names[$element['name']['#value']])) {
  772. form_error($element['name'], t('The machine-readable name %name is already taken.', array('%name' => $element['name']['#value'])));
  773. }
  774. $names[$element['name']['#value']] = TRUE;
  775. }
  776. }
  777. }
  778. /**
  779. * Helper to sort elements by their 'weight' key.
  780. */
  781. function rules_element_sort_helper($a, $b) {
  782. $a += array('weight' => 0);
  783. $b += array('weight' => 0);
  784. if ($a['weight'] == $b['weight']) {
  785. return 0;
  786. }
  787. return ($a['weight'] < $b['weight']) ? -1 : 1;
  788. }
  789. /**
  790. * Form after build handler to set the static base path.
  791. *
  792. * @see RulesPluginUI::formDefaults()
  793. */
  794. function rules_form_after_build_restore_base_path($form, &$form_state) {
  795. if (isset($form_state['_rules_base_path'])) {
  796. RulesPluginUI::$basePath = $form_state['_rules_base_path'];
  797. }
  798. return $form;
  799. }
  800. /**
  801. * AJAX page callback to load tag suggestions.
  802. *
  803. * Largely copied from taxonomy_autocomplete().
  804. */
  805. function rules_autocomplete_tags($tags_typed = '') {
  806. // The user enters a comma-separated list of tags. We only autocomplete the
  807. // last tag.
  808. $tags_typed = drupal_explode_tags($tags_typed);
  809. $tag_last = drupal_strtolower(array_pop($tags_typed));
  810. $tag_matches = array();
  811. if ($tag_last != '') {
  812. $query = db_select('rules_tags', 'rt');
  813. // Do not select already entered terms.
  814. if (!empty($tags_typed)) {
  815. $query->condition('rt.tag', $tags_typed, 'NOT IN');
  816. }
  817. // Select rows that match by tag name.
  818. $tags_return = $query
  819. ->distinct()
  820. ->fields('rt', array('tag'))
  821. ->condition('rt.tag', '%' . db_like($tag_last) . '%', 'LIKE')
  822. ->groupBy('rt.tag')
  823. ->range(0, 10)
  824. ->execute()
  825. ->fetchCol('rt.tag');
  826. $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
  827. foreach ($tags_return as $name) {
  828. $n = $name;
  829. // Tag names containing commas or quotes must be wrapped in quotes.
  830. if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
  831. $n = '"' . str_replace('"', '""', $name) . '"';
  832. }
  833. $tag_matches[$prefix . $n] = check_plain($name);
  834. }
  835. }
  836. drupal_json_output($tag_matches);
  837. }