Projet

Général

Profil

Paste
Télécharger (33,9 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / rules / ui / ui.forms.inc @ 76e2e7c3

1
<?php
2

    
3
/**
4
 * @file Rules UI forms
5
 */
6

    
7
/**
8
 * Ajax callback for reloading the whole form.
9
 */
10
function rules_ui_form_ajax_reload_form($form, $form_state) {
11
  return $form;
12
}
13

    
14
/**
15
 * Defines #ajax properties.
16
 */
17
function rules_ui_form_default_ajax($effect = 'slide') {
18
  return array(
19
    'callback' => 'rules_ui_form_ajax_reload_form',
20
    'wrapper' => 'rules-form-wrapper',
21
    'effect' => $effect,
22
    'speed' => 'fast',
23
  );
24
}
25

    
26
/**
27
 * Submit handler for switching the parameter input mode.
28
 */
29
function rules_ui_parameter_replace_submit($form, &$form_state) {
30
  if (isset($form_state['triggering_element'])) {
31
    $name = $form_state['triggering_element']['#parameter'];
32
    $form_state['parameter_mode'][$name] = $form_state['parameter_mode'][$name] == 'selector' ? 'input' : 'selector';
33
  }
34
  $form_state['rebuild'] = TRUE;
35
}
36

    
37
/**
38
 * General form submit handler, that rebuilds the form
39
 */
40
function rules_form_submit_rebuild($form, &$form_state) {
41
  $form_state['rebuild'] = TRUE;
42
}
43

    
44
/**
45
 * Edit a rules configuration.
46
 */
47
function rules_ui_form_edit_rules_config($form, &$form_state, $rules_config, $base_path) {
48
  RulesPluginUI::$basePath = $base_path;
49
  $form_state += array('rules_element' => $rules_config);
50
  // Add the rule configuration's form.
51
  $rules_config->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE));
52
  $form['#validate'] = array('rules_ui_form_rules_config_validate');
53
  return $form;
54
}
55

    
56
/**
57
 * General rules configuration form validation callback. Also populates the
58
 * rules configuration with the form values.
59
 */
60
function rules_ui_form_rules_config_validate($form, &$form_state) {
61
  $form_state['rules_element']->form_validate($form, $form_state);
62
}
63

    
64
/**
65
 * Edit a rules configuration form submit callback.
66
 */
67
function rules_ui_form_edit_rules_config_submit($form, &$form_state) {
68
  $form_state['rules_element']->form_submit($form, $form_state);
69
  drupal_set_message(t('Your changes have been saved.'));
70
  if (empty($form_state['redirect'])) {
71
    $form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['rules_element']);
72
  }
73
}
74

    
75
/**
76
 * Clone a rules configuration form.
77
 */
78
function rules_ui_form_clone_rules_config($form, &$form_state, $rules_config, $base_path) {
79
  RulesPluginUI::$basePath = $base_path;
80
  $rules_config = clone $rules_config;
81
  $rules_config->id = NULL;
82
  $rules_config->name = '';
83
  $rules_config->label .= ' (' . t('cloned') . ')';
84
  $rules_config->status = ENTITY_CUSTOM;
85

    
86
  $form['#validate'][] = 'rules_ui_form_rules_config_validate';
87
  $form['#submit'][] = 'rules_ui_form_edit_rules_config_submit';
88
  $form_state += array('rules_element' => $rules_config, 'op' => 'clone');
89

    
90
  // Add the rule configuration's form.
91
  $rules_config->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE, 'init' => TRUE));
92

    
93
  // Open the settings fieldset so altering the name is easier.
94
  $form['settings']['#collapsed'] = FALSE;
95
  return $form;
96
}
97

    
98
/**
99
 * A simple form just showing a textarea with the export.
100
 */
101
function rules_ui_form_export_rules_config($form, &$form_state, $rules_config, $base_path) {
102
  $form['export'] = array(
103
    '#type' => 'textarea',
104
    '#title' => t('Export'),
105
    '#description' => t('For importing copy the content of the text area and paste it into the import page.'),
106
    '#rows' => 25,
107
    '#default_value' => $rules_config->export(),
108
  );
109
  return $form;
110
}
111

    
112
/**
113
 * Configuration form to directly execute a rules configuration.
114
 */
115
function rules_ui_form_execute_rules_config($form, &$form_state, $rules_config, $base_path) {
116
  // Only components can be executed.
117
  if (!($rules_config instanceof RulesTriggerableInterface)) {
118
    RulesPluginUI::$basePath = $base_path;
119
    // Create either the appropriate action or condition element.
120
    $element = rules_plugin_factory($rules_config instanceof RulesActionInterface ? 'action' : 'condition', 'component_' . $rules_config->name);
121
    $form['exec_help'] = array(
122
      '#prefix' => '<p>',
123
      '#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())),
124
      '#suffix' => '</p>',
125
    );
126
    $element->form($form, $form_state);
127

    
128
    // For conditions hide the option to negate them.
129
    if (isset($form['negate'])) {
130
      $form['negate']['#access'] = FALSE;
131
    }
132
    $form['submit'] = array(
133
      '#type' => 'submit',
134
      '#value' => t('Execute'),
135
      '#weight' => 20,
136
    );
137
    // Re-use the validation callback, which will also populate the action with
138
    // the configuration settings in the form.
139
    $form['#validate'] = array('rules_ui_form_rules_config_validate');
140
    return $form;
141
  }
142
  drupal_not_found();
143
  exit;
144
}
145

    
146
/**
147
 * Submit callback for directly executing a component.
148
 */
149
function rules_ui_form_execute_rules_config_submit($form, &$form_state) {
150
  $element = $form_state['rules_element'];
151
  $result = $element->execute();
152
  if ($element instanceof RulesActionInterface) {
153
    drupal_set_message(t('Component %label has been executed.', array('%label' => $element->label())));
154
  }
155
  else {
156
    drupal_set_message(t('Component %label evaluated to %result.', array('%label' => $element->label(), '%result' => $result ? 'true' : 'false')));
157
  }
158
}
159

    
160
/**
161
 * Gets the confirmation question for valid operations, or else FALSE.
162
 */
163
function rules_ui_confirm_operations($op, $rules_config) {
164
  $vars = array('%plugin' => $rules_config->plugin(), '%label' => $rules_config->label());
165

    
166
  switch ($op) {
167
    case 'enable':
168
      return array(t('Are you sure you want to enable the %plugin %label?', $vars), '');
169
    case 'disable':
170
      return array(t('Are you sure you want to disable the %plugin %label?', $vars), '');
171
    case 'revert':
172
      return array(t('Are you sure you want to revert the %plugin %label?', $vars), t('This action cannot be undone.'));
173
    case 'delete':
174
      return array(t('Are you sure you want to delete the %plugin %label?', $vars), t('This action cannot be undone.'));
175
    default:
176
      return FALSE;
177
  }
178
}
179

    
180
/**
181
 * Confirmation form for applying the operation to the config.
182
 */
183
function rules_ui_form_rules_config_confirm_op($form, &$form_state, $rules_config, $op, $base_path) {
184
  if (list($confirm_question, $description) = rules_ui_confirm_operations($op, $rules_config)) {
185
    RulesPluginUI::$basePath = $base_path;
186
    $form_state += array('rules_config' => $rules_config, 'op' => $op);
187
    return confirm_form($form, $confirm_question, $base_path, $description, t('Confirm'), t('Cancel'));
188
  }
189
  else {
190
    drupal_not_found();
191
    exit;
192
  }
193
}
194

    
195
/**
196
 * Applies the operation and returns the message to show to the user. Also the
197
 * operation is logged to the watchdog. Note that the string is defined two
198
 * times so that the translation extractor can find it.
199
 */
200
function rules_ui_confirm_operation_apply($op, $rules_config) {
201
  $vars = array('%plugin' => $rules_config->plugin(), '%label' => $rules_config->label());
202
  $edit_link = l(t('edit'), RulesPluginUI::path($rules_config->name));
203

    
204
  switch ($op) {
205
    case 'enable':
206
      $rules_config->active = TRUE;
207
      $rules_config->save();
208
      watchdog('rules', 'Enabled %plugin %label.', $vars, WATCHDOG_NOTICE, $edit_link);
209
      return t('Enabled %plugin %label.', $vars);
210

    
211
    case 'disable':
212
      $rules_config->active = FALSE;
213
      $rules_config->save();
214
      watchdog('rules', 'Disabled %plugin %label.', $vars, WATCHDOG_NOTICE, $edit_link);
215
      return t('Disabled %plugin %label.', $vars);
216

    
217
    case 'revert':
218
      $rules_config->delete();
219
      watchdog('rules', 'Reverted %plugin %label to the defaults.', $vars, WATCHDOG_NOTICE, $edit_link);
220
      return t('Reverted %plugin %label to the defaults.', $vars);
221

    
222
    case 'delete':
223
      $rules_config->delete();
224
      watchdog('rules', 'Deleted %plugin %label.', $vars);
225
      return t('Deleted %plugin %label.', $vars);
226
  }
227
}
228

    
229
/**
230
 * Rule config deletion form submit callback.
231
 */
232
function rules_ui_form_rules_config_confirm_op_submit($form, &$form_state) {
233
  if ($form_state['values']['confirm']) {
234
    $msg = rules_ui_confirm_operation_apply($form_state['op'], $form_state['rules_config']);
235
    drupal_set_message($msg);
236
  }
237
}
238

    
239
/**
240
 * Add a new element a rules configuration.
241
 */
242
function rules_ui_add_element($form, &$form_state, $rules_config, $plugin_name, RulesContainerPlugin $parent, $base_path) {
243
  $cache = rules_get_cache();
244
  if (!isset($cache['plugin_info'][$plugin_name]['class'])) {
245
    drupal_not_found();
246
    exit;
247
  }
248
  RulesPluginUI::$basePath = $base_path;
249
  $plugin_is_abstract = in_array('RulesAbstractPlugin', class_parents($cache['plugin_info'][$plugin_name]['class']));
250
  // In the first step create the element and in the second step show its edit
251
  // form.
252
  if ($plugin_is_abstract && !isset($form_state['rules_element'])) {
253
    RulesPluginUI::formDefaults($form, $form_state);
254
    $form_state += array('parent_element' => $parent, 'plugin' => $plugin_name);
255

    
256
    $form['element_name'] = array(
257
      '#type' => 'select',
258
      '#title' => t('Select the %element to add', array('%element' => $plugin_name)),
259
      '#options' => RulesPluginUI::getOptions($plugin_name),
260
      '#ajax' => rules_ui_form_default_ajax() + array(
261
        'trigger_as' => array('name' => 'continue'),
262
      ),
263
    );
264
    $form['continue'] = array(
265
      '#type' => 'submit',
266
      '#name' => 'continue',
267
      '#value' => t('Continue'),
268
      '#ajax' => rules_ui_form_default_ajax(),
269
    );
270
  }
271
  elseif (!$plugin_is_abstract) {
272
    // Create the initial, empty element.
273
    $element = rules_plugin_factory($plugin_name);
274
    // Always add the new element at the bottom, thus set an appropriate weight.
275
    $iterator = $parent->getIterator();
276
    if ($sibling = end($iterator)) {
277
      $element->weight = $sibling->weight + 1;
278
    }
279
    $element->setParent($parent);
280
    $form_state['rules_element'] = $element;
281
  }
282

    
283
  if (isset($form_state['rules_element'])) {
284
    $form_state['rules_element']->form($form, $form_state, array('button' => TRUE, 'init' => TRUE));
285
    $form['#validate'][] = 'rules_ui_edit_element_validate';
286
    $form['#submit'][] = 'rules_ui_edit_element_submit';
287
  }
288
  return $form;
289
}
290

    
291
/**
292
 * Add element submit callback.
293
 * Used for "abstract plugins" to create the initial element object with the
294
 * given implemenation name and rebuild the form.
295
 */
296
function rules_ui_add_element_submit($form, &$form_state) {
297
  $element = rules_plugin_factory($form_state['plugin'], $form_state['values']['element_name']);
298

    
299
  // Always add the new element at the bottom, thus set an appropriate weight.
300
  $iterator = $form_state['parent_element']->getIterator();
301
  if ($sibling = end($iterator)) {
302
    $element->weight = $sibling->weight + 1;
303
  }
304
  // Clear the element settings so they won't be processed on serialization as
305
  // there is nothing to be processed yet.
306
  $element->settings = array();
307
  $element->setParent($form_state['parent_element']);
308

    
309
  $form_state['rules_element'] = $element;
310
  $form_state['rebuild'] = TRUE;
311
}
312

    
313
/**
314
 * Delete elements.
315
 */
316
function rules_ui_delete_element($form, &$form_state, $rules_config, $rules_element, $base_path) {
317
  RulesPluginUI::$basePath = $base_path;
318

    
319
  if (empty($form_state['rules_config'])) {
320
    // Before modifying the rules config we have to clone it, so any
321
    // modifications won't appear in the static cache of the loading controller.
322
    $rules_config = clone $rules_config;
323
    // Also get the element from the cloned config.
324
    $rules_element = $rules_config->elementMap()->lookup($rules_element->elementId());
325

    
326
    $form_state['rules_config'] = $rules_config;
327
    $form_state['rules_element'] = $rules_element;
328
    $form_state['element_parent'] = $rules_element->parentElement();
329
  }
330

    
331
  // Try deleting the element and warn the user if something breaks, but
332
  // save the parent for determining the right redirect target on submit.
333
  $removed_plugin = $form_state['rules_element']->plugin();
334
  $rules_element->delete();
335

    
336
  if (empty($rules_config->dirty) && empty($form_state['input'])) {
337
    try {
338
      $rules_config->integrityCheck();
339
    }
340
    catch (RulesIntegrityException $e) {
341
      $args = array(
342
        '@plugin' => $e->element->plugin(),
343
        '%label' => $e->element->label(),
344
        '@removed-plugin' => $removed_plugin,
345
        '!url' => url(RulesPluginUI::path($form_state['rules_config']->name, 'edit', $e->element)),
346
      );
347
      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');
348
    }
349
  }
350

    
351
  $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()));
352
  return confirm_form($form, $confirm_question, RulesPluginUI::path($rules_config->name), t('This action cannot be undone.'), t('Delete'), t('Cancel'));
353
}
354

    
355
/**
356
 * Rule config deletion form submit callback.
357
 */
358
function rules_ui_delete_element_submit($form, &$form_state) {
359
  $rules_config = $form_state['rules_config'];
360
  $rules_config->save();
361
  if (empty($form_state['redirect'])) {
362
    $form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['element_parent']);
363
  }
364
}
365

    
366

    
367
/**
368
 * Configure a rule element.
369
 */
370
function rules_ui_edit_element($form, &$form_state, $rules_config, $element, $base_path) {
371
  RulesPluginUI::$basePath = $base_path;
372
  $form_state += array('rules_element' => $element);
373
  $form_state['rules_element']->form($form, $form_state, array('button' => TRUE));
374
  return $form;
375
}
376

    
377
/**
378
 * Validate the element configuration.
379
 */
380
function rules_ui_edit_element_validate($form, &$form_state) {
381
  $form_state['rules_element']->form_validate($form, $form_state);
382
}
383

    
384
/**
385
 * Submit the element configuration.
386
 */
387
function rules_ui_edit_element_submit($form, &$form_state) {
388
  $form_state['rules_element']->form_submit($form, $form_state);
389
  drupal_set_message(t('Your changes have been saved.'));
390
  if (empty($form_state['redirect'])) {
391
    $form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['rules_element']);
392
  }
393
}
394

    
395
/**
396
 * Form builder for the "add event" page.
397
 */
398
function rules_ui_add_event_page($form, &$form_state, RulesTriggerableInterface $rules_config, $base_path) {
399
  RulesPluginUI::$basePath = $base_path;
400
  RulesPluginUI::formDefaults($form, $form_state);
401
  $form = rules_ui_add_event($form, $form_state, $rules_config, $base_path);
402
  $form['#validate'][] = 'rules_ui_add_event_validate';
403
  return $form;
404
}
405

    
406
/**
407
 * Submit the event configuration.
408
 */
409
function rules_ui_add_event_page_submit($form, &$form_state) {
410
  rules_ui_add_event_apply($form, $form_state);
411
  $rules_config = $form_state['rules_config'];
412

    
413
  // Tell the user if this breaks something, but let him proceed.
414
  if (empty($rules_config->dirty)) {
415
    try {
416
      $rules_config->integrityCheck();
417
    }
418
    catch (RulesIntegrityException $e) {
419
      $warning = TRUE;
420
      drupal_set_message(t('Added the event, but it does not provide all variables utilized.'), 'warning');
421
    }
422
  }
423
  $rules_config->save();
424
  if (!isset($warning)) {
425
    $events = rules_fetch_data('event_info');
426
    $label = $events[$form_state['values']['event']]['label'];
427
    drupal_set_message(t('Added event %event.', array('%event' => $label)));
428
  }
429
}
430

    
431
/**
432
 * Add a new event.
433
 */
434
function rules_ui_add_event($form, &$form_state, RulesReactionRule $rules_config, $base_path) {
435
  $form_state += array('rules_config' => $rules_config);
436
  $events = array_diff_key(rules_fetch_data('event_info'), array_flip($rules_config->events()));
437

    
438
  $form['help'] = array(
439
    '#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.'),
440
  );
441
  $form['event'] = array(
442
    '#type' => 'select',
443
    '#title' => t('React on event'),
444
    '#options' => RulesPluginUI::getOptions('event', $events),
445
    '#description' => t('Whenever the event occurs, rule evaluation is triggered.'),
446
    '#ajax' => rules_ui_form_default_ajax(),
447
    '#required' => TRUE,
448
  );
449
  if (!empty($form_state['values']['event'])) {
450
    $handler = rules_get_event_handler($form_state['values']['event']);
451
    $form['event_settings'] = $handler->buildForm($form_state);
452
  }
453
  else {
454
    $form['event_settings'] = array();
455
  }
456
  $form['submit'] = array(
457
    '#type' => 'submit',
458
    '#value' => t('Add'),
459
  );
460
  $form_state['redirect'] = RulesPluginUI::path($rules_config->name);
461
  return $form;
462
}
463

    
464
/**
465
 * Validation callback for adding an event.
466
 */
467
function rules_ui_add_event_validate($form, $form_state) {
468
  $handler = rules_get_event_handler($form_state['values']['event']);
469
  $handler->extractFormValues($form['event_settings'], $form_state);
470
  try {
471
    $handler->validate();
472
  }
473
  catch (RulesIntegrityException $e) {
474
    form_set_error(implode('][', $e->keys), $e->getMessage());
475
  }
476
}
477

    
478
/**
479
 * Submit callback that just adds the selected event.
480
 *
481
 * @see rules_admin_add_reaction_rule()
482
 */
483
function rules_ui_add_event_apply($form, &$form_state) {
484
  $handler = rules_get_event_handler($form_state['values']['event']);
485
  $handler->extractFormValues($form['event_settings'], $form_state);
486
  $form_state['rules_config']->event($form_state['values']['event'], $handler->getSettings());
487
}
488

    
489
/**
490
 * Form to remove a event from a rule.
491
 */
492
function rules_ui_remove_event($form, &$form_state, $rules_config, $event, $base_path) {
493
  RulesPluginUI::$basePath = $base_path;
494
  $form_state += array('rules_config' => $rules_config, 'rules_event' => $event);
495
  $event_info = rules_get_event_info($event);
496
  $form_state['event_label'] = $event_info['label'];
497
  $confirm_question = t('Are you sure you want to remove the event?');
498
  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'));
499
}
500

    
501
/**
502
 * Submit the event configuration.
503
 */
504
function rules_ui_remove_event_submit($form, &$form_state) {
505
  $rules_config = $form_state['rules_config'];
506
  $rules_config->removeEvent($form_state['rules_event']);
507
  // Tell the user if this breaks something, but let him proceed.
508
  if (empty($rules_config->dirty)) {
509
    try {
510
      $rules_config->integrityCheck();
511
    }
512
    catch (RulesIntegrityException $e) {
513
      $warning = TRUE;
514
      drupal_set_message(t('Removed the event, but it had provided some variables which are now missing.'), 'warning');
515
    }
516
  }
517
  $rules_config->save();
518
  if (!isset($warning)) {
519
    drupal_set_message(t('Event %event has been removed.', array('%event' => $form_state['event_label'])));
520
  }
521
  $form_state['redirect'] = RulesPluginUI::path($rules_config->name);
522
}
523

    
524
/**
525
 * Import form for rule configurations.
526
 */
527
function rules_ui_import_form($form, &$form_state, $base_path) {
528
  RulesPluginUI::$basePath = $base_path;
529
  RulesPluginUI::formDefaults($form, $form_state);
530
  $form['import'] = array(
531
    '#type' => 'textarea',
532
    '#title' => t('Import'),
533
    '#description' => t('Paste an exported Rules configuration here.'),
534
    '#rows' => 20,
535
  );
536
  $form['overwrite'] = array(
537
    '#title' => t('Overwrite'),
538
    '#type' => 'checkbox',
539
    '#description' => t('If checked, any existing configuration with the same identifier will be replaced by the import.'),
540
    '#default_value' => FALSE,
541
  );
542
  $form['submit'] = array(
543
    '#type' => 'submit',
544
    '#value' => t('Import'),
545
  );
546
  return $form;
547
}
548

    
549
/**
550
 * Validation callback for the import form.
551
 */
552
function rules_ui_import_form_validate($form, &$form_state) {
553
  if ($rules_config = rules_import($form_state['values']['import'], $error_msg)) {
554
    // Store the successfully imported entity in $form_state.
555
    $form_state['rules_config'] = $rules_config;
556
    if (!$form_state['values']['overwrite']) {
557
      // Check for existing entities with the same identifier.
558
      if (rules_config_load($rules_config->name)) {
559
        $vars = array('@entity' => t('Rules configuration'), '%label' => $rules_config->label());
560
        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));
561
      }
562
    }
563
    try {
564
      $rules_config->integrityCheck();
565
    }
566
    catch (RulesIntegrityException $e) {
567
      form_set_error('import', t('Integrity check for the imported configuration failed. Error message: %message.', array('%message' => $e->getMessage())));
568
    }
569
    if (!user_access('bypass rules access') && !$rules_config->access()) {
570
      form_set_error('import', t('You have insufficient access permissions for importing this Rules configuration.'));
571
    }
572
  }
573
  else {
574
    form_set_error('import', t('Import failed.'));
575
    if ($error_msg) {
576
      drupal_set_message($error_msg, 'error');
577
    }
578
  }
579
}
580

    
581
/**
582
 * Submit callback for the import form.
583
 */
584
function rules_ui_import_form_submit($form, &$form_state) {
585
  $rules_config = $form_state['rules_config'];
586

    
587
  if ($existing_config = rules_config_load($rules_config->name)) {
588
    // Copy DB id and remove the new indicator to overwrite the existing record.
589
    $rules_config->id = $existing_config->id;
590
    unset($rules_config->is_new);
591
  }
592
  $rules_config->save();
593
  $vars = array('@entity' => t('Rules configuration'), '%label' => $rules_config->label());
594
  watchdog('rules_config', 'Imported @entity %label.', $vars);
595
  drupal_set_message(t('Imported @entity %label.', $vars));
596
  $form_state['redirect'] = RulesPluginUI::$basePath;
597
}
598

    
599
/**
600
 * FAPI process callback for the data selection widget.
601
 * This finalises the auto completion callback path by appending the form build
602
 * id.
603
 */
604
function rules_data_selection_process($element, &$form_state, $form) {
605
  $element['#autocomplete_path'] .= '/' . $form['#build_id'];
606
  $form_state['cache'] = TRUE;
607
  return $element;
608
}
609

    
610
/**
611
 * Autocomplete data selection results.
612
 */
613
function rules_ui_form_data_selection_auto_completion($parameter, $form_build_id, $string = '') {
614
  // Get the form and its state from the cache to get the currently edited
615
  // or created element.
616
  $form_state = form_state_defaults();
617
  $form = form_get_cache($form_build_id, $form_state);
618
  if (!isset($form_state['rules_element'])) {
619
    return;
620
  }
621
  $element = $form_state['rules_element'];
622

    
623
  $params = $element->pluginParameterInfo();
624
  $matches = array();
625
  if (isset($params[$parameter])) {
626
    $parts = explode(':', $string);
627
    // Remove the last part as it might be unfinished.
628
    $last_part = array_pop($parts);
629
    $selector = implode(':', $parts);
630

    
631
    // Start with the partly given selector or from scratch.
632
    $result = array();
633
    if ($selector && $wrapper = $element->applyDataSelector($selector)) {
634
      $result = RulesData::matchingDataSelector($wrapper, $params[$parameter], $selector . ':', 0);
635
    }
636
    elseif (!$selector) {
637
      $result = RulesData::matchingDataSelector($element->availableVariables(), $params[$parameter], '', 0);
638
    }
639

    
640
    foreach ($result as $selector => $info) {
641
      // If we have an uncomplete last part, take it into account now.
642
      $attributes = array();
643
      if (!$last_part || strpos($selector, $string) === 0) {
644
        $attributes['class'][] = 'rules-dsac-item';
645
        $attributes['title'] = isset($info['description']) ? strip_tags($info['description']) : '';
646
        if ($selector[strlen($selector) - 1] == ':') {
647
          $attributes['class'][] = 'rules-dsac-group';
648
          $text = check_plain($selector) . '... (' . check_plain($info['label']) . ')';
649
        }
650
        else {
651
          $text = check_plain($selector) . ' (' . check_plain($info['label']) . ')';
652
        }
653
        $matches[$selector] = "<div" . drupal_attributes($attributes) . ">$text</div>";
654
      }
655
    }
656
  }
657
  drupal_json_output($matches);
658
}
659

    
660

    
661
/**
662
 * FAPI validation of an integer element. Copy of the private function
663
 * _element_validate_integer().
664
 */
665
function rules_ui_element_integer_validate($element, &$form_state) {;
666
  $value = $element['#value'];
667
  if (isset($value) && $value !== '' && (!is_numeric($value) || intval($value) != $value)) {
668
    form_error($element, t('%name must be an integer value.', array('%name' => isset($element['#title']) ? $element['#title'] : t('Element'))));
669
  }
670
}
671

    
672
/**
673
 * FAPI validation of a decimal element. Improved version of the private
674
 * function _element_validate_number().
675
 */
676
function rules_ui_element_decimal_validate($element, &$form_state) {
677
  // Substitute the decimal separator ",".
678
  $value = strtr($element['#value'], ',', '.');
679
  if ($value != '' && !is_numeric($value)) {
680
    form_error($element, t('%name must be a number.', array('%name' => $element['#title'])));
681
  }
682
  elseif ($value != $element['#value']) {
683
    form_set_value($element, $value, $form_state);
684
  }
685
}
686

    
687
/**
688
 * FAPI callback to validate an IP address.
689
 */
690
function rules_ui_element_ip_address_validate($element, &$form_state) {
691
  $value = $element['#value'];
692
  if ($value != '' && !filter_var($value, FILTER_VALIDATE_IP)) {
693
    form_error($element, t('%name is not a valid IP address.', array('%name' => $element['#title'])));
694
  }
695
}
696

    
697
/**
698
 * FAPI validation of a date element. Makes sure the specified date format is
699
 * correct and converts date values specifiy a fixed (= non relative) date to
700
 * a timestamp. Relative dates are handled by the date input evaluator.
701
 */
702
function rules_ui_element_date_validate($element, &$form_state) {
703
  $value = $element['#value'];
704
  if ($value == '' || (is_numeric($value) && intval($value) == $value)) {
705
    // The value is a timestamp.
706
    return;
707
  }
708
  elseif (is_string($value) && RulesDateInputEvaluator::gmstrtotime($value) === FALSE) {
709
    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))));
710
  }
711
  elseif (is_string($value) && RulesDateInputEvaluator::isFixedDateString($value)) {
712
    // As the date string specifies a fixed format, we can convert it now.
713
    $value = RulesDateInputEvaluator::gmstrtotime($value);
714
    form_set_value($element, $value, $form_state);
715
  }
716
}
717

    
718
/**
719
 * FAPI process callback for the duration element type.
720
 */
721
function rules_ui_element_duration_process($element, &$form_state) {
722
  $element['value'] = array(
723
    '#type' => 'textfield',
724
    '#size' => 8,
725
    '#element_validate' => array('rules_ui_element_integer_validate'),
726
    '#default_value' => $element['#default_value'],
727
    '#required' => !empty($element['#required']),
728
  );
729
  $element['multiplier'] = array(
730
    '#type' => 'select',
731
    '#options' => rules_ui_element_duration_multipliers(),
732
    '#default_value' => 1,
733
  );
734

    
735
  // Put the child elements in a container-inline div.
736
  $element['value']['#prefix'] = '<div class="rules-duration container-inline">';
737
  $element['multiplier']['#suffix'] = '</div>';
738

    
739
  // Set an appropriate multiplier.
740
  if (!empty($element['value']['#default_value'])) {
741
    foreach (array_keys(rules_ui_element_duration_multipliers()) as $m) {
742
      if ($element['value']['#default_value'] % $m == 0) {
743
        $element['multiplier']['#default_value'] = $m;
744
      }
745
    }
746
    // Divide value by the multiplier, so the display is correct.
747
    $element['value']['#default_value'] /= $element['multiplier']['#default_value'];
748
  }
749
  return $element;
750
}
751

    
752
/**
753
 * Defines possible duration multiplier.
754
 */
755
function rules_ui_element_duration_multipliers() {
756
  return array(
757
    1 => t('seconds'),
758
    60 => t('minutes'),
759
    3600 => t('hours'),
760
    // Just use approximate numbers for days (might last 23h on DST change),
761
    // months and years.
762
    86400 => t('days'),
763
    86400 * 30 => t('months'),
764
    86400 * 30 * 12 => t('years'),
765
  );
766
}
767

    
768
/**
769
 * Helper function to determine the value for a rules duration form
770
 * element.
771
 */
772
function rules_ui_element_duration_value($element, $input = FALSE) {
773
  // This runs before child elements are processed, so we cannot calculate the
774
  // value here. But we have to make sure the value is an array, so the form
775
  // API is able to process the children to set their values in the array. Thus
776
  // once the form API has finished processing the element, the value is an
777
  // array containing the child element values. Then finally the after build
778
  // callback converts it back to the numeric value and sets that.
779
  return array();
780
}
781

    
782
/**
783
 * FAPI after build callback for the duration parameter type form.
784
 * Fixes up the form value by applying the multiplier.
785
 */
786
function rules_ui_element_duration_after_build($element, &$form_state) {
787
  if ($element['value']['#value'] !== '') {
788
    $element['#value'] = $element['value']['#value'] * $element['multiplier']['#value'];
789
    form_set_value($element, $element['#value'], $form_state);
790
  }
791
  else {
792
    $element['#value'] = NULL;
793
    form_set_value($element, NULL, $form_state);
794
  }
795
  return $element;
796
}
797

    
798
/**
799
 * FAPI after build callback to ensure empty form elements result in no value.
800
 */
801
function rules_ui_element_fix_empty_after_build($element, &$form_state) {
802
  if (isset($element['#value']) && $element['#value'] === '') {
803
    $element['#value'] = NULL;
804
    form_set_value($element, NULL, $form_state);
805
  }
806
  // Work-a-round for the text_format element.
807
  elseif ($element['#type'] == 'text_format' && !isset($element['value']['#value'])) {
808
    form_set_value($element, NULL, $form_state);
809
  }
810
  return $element;
811
}
812

    
813

    
814
/**
815
 * FAPI after build callback for specifying a list of values.
816
 *
817
 * Turns the textual value in an array by splitting the text in chunks using the
818
 * delimiter set at $element['#delimiter'].
819
 */
820
function rules_ui_list_textarea_after_build($element, &$form_state) {
821
  $element['#value'] = $element['#value'] ? explode($element['#delimiter'], $element['#value']) : array();
822
  $element['#value'] = array_map('trim', $element['#value']);
823
  form_set_value($element, $element['#value'], $form_state);
824
  return $element;
825
}
826

    
827
/**
828
 * FAPI pre render callback. Turns the value back to a string for rendering.
829
 *
830
 * @see rules_ui_list_textarea_after_build()
831
 */
832
function rules_ui_list_textarea_pre_render($element) {
833
  $element['#value'] = implode($element['#delimiter'], $element['#value']);
834
  return $element;
835
}
836

    
837
/**
838
 * FAPI callback to validate a list of integers.
839
 */
840
function rules_ui_element_integer_list_validate($element, &$form_state) {
841
  foreach ($element['#value'] as $value) {
842
    if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) {
843
      form_error($element, t('Each value must be an integer.'));
844
    }
845
  }
846
}
847

    
848
/**
849
 * FAPI callback to validate a token.
850
 */
851
function rules_ui_element_token_validate($element) {
852
  $value = $element['#value'];
853
  if (isset($value) && $value !== '' && !entity_property_verify_data_type($value, 'token')) {
854
    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'))));
855
  }
856
}
857

    
858
/**
859
 * FAPI callback to validate a list of tokens.
860
 */
861
function rules_ui_element_token_list_validate($element, &$form_state) {
862
  foreach ($element['#value'] as $value) {
863
    if ($value !== '' && !entity_property_verify_data_type($value, 'token')) {
864
      form_error($element, t('Each value may only contain lowercase letters, numbers, and underscores and has to start with a letter.'));
865
    }
866
  }
867
}
868

    
869
/**
870
 * FAPI callback to validate a machine readable name.
871
 */
872
function rules_ui_element_machine_name_validate($element, &$form_state) {
873
  if ($element['#value'] && !preg_match('!^[a-z0-9_]+$!', $element['#value'])) {
874
    form_error($element, t('Machine-readable names must contain only lowercase letters, numbers, and underscores.'));
875
  }
876
}
877

    
878
/**
879
 * FAPI callback to validate the form for editing variable info.
880
 * @see RulesPluginUI::getVariableForm()
881
 */
882
function rules_ui_element_variable_form_validate($elements, &$form_state) {
883
  $names = array();
884
  foreach (element_children($elements['items']) as $item_key) {
885
    $element = &$elements['items'][$item_key];
886
    if ($element['name']['#value'] || $element['type']['#value'] || $element['label']['#value']) {
887
      foreach (array('name' => t('Machine name'), 'label' => t('Label'), 'type' => t('Data type')) as $key => $title) {
888
        if (!$element[$key]['#value']) {
889
          form_error($element[$key], t('!name field is required.', array('!name' => $title)));
890
        }
891
      }
892
      if (isset($names[$element['name']['#value']])) {
893
        form_error($element['name'], t('The machine-readable name %name is already taken.', array('%name' => $element['name']['#value'])));
894
      }
895
      $names[$element['name']['#value']] = TRUE;
896
    }
897
  }
898
}
899

    
900
/**
901
 * Helper to sort elements by their 'weight' key.
902
 */
903
function rules_element_sort_helper($a, $b) {
904
  $a += array('weight' => 0);
905
  $b += array('weight' => 0);
906
  if ($a['weight'] == $b['weight']) {
907
    return 0;
908
  }
909
  return ($a['weight'] < $b['weight']) ? -1 : 1;
910
}
911

    
912
/**
913
 * Form after build handler to set the static base path.
914
 *
915
 * @see RulesPluginUI::formDefaults()
916
 */
917
function rules_form_after_build_restore_base_path($form, &$form_state) {
918
  if (isset($form_state['_rules_base_path'])) {
919
    RulesPluginUI::$basePath = $form_state['_rules_base_path'];
920
  }
921
  return $form;
922
}
923

    
924
/**
925
 * AJAX page callback to load tag suggestions.
926
 *
927
 * Largely copied from taxonomy_autocomplete().
928
 */
929
function rules_autocomplete_tags($tags_typed = '') {
930
  // The user enters a comma-separated list of tags. We only autocomplete the
931
  // last tag.
932
  $tags_typed = drupal_explode_tags($tags_typed);
933
  $tag_last = drupal_strtolower(array_pop($tags_typed));
934

    
935
  $tag_matches = array();
936
  if ($tag_last != '') {
937
    $query = db_select('rules_tags', 'rt');
938
    // Do not select already entered terms.
939
    if (!empty($tags_typed)) {
940
      $query->condition('rt.tag', $tags_typed, 'NOT IN');
941
    }
942
    // Select rows that match by tag name.
943
    $tags_return = $query
944
      ->distinct()
945
      ->fields('rt', array('tag'))
946
      ->condition('rt.tag', '%' . db_like($tag_last) . '%', 'LIKE')
947
      ->groupBy('rt.tag')
948
      ->range(0, 10)
949
      ->execute()
950
      ->fetchCol('rt.tag');
951

    
952
    $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
953

    
954
    foreach ($tags_return as $name) {
955
      $n = $name;
956
      // Tag names containing commas or quotes must be wrapped in quotes.
957
      if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) {
958
        $n = '"' . str_replace('"', '""', $name) . '"';
959
      }
960
      $tag_matches[$prefix . $n] = check_plain($name);
961
    }
962
  }
963
  drupal_json_output($tag_matches);
964
}