ui.core.inc 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171
  1. <?php
  2. /**
  3. * @file Contains core ui functions.
  4. */
  5. /**
  6. * Plugin UI Interface.
  7. */
  8. interface RulesPluginUIInterface {
  9. /**
  10. * Adds the whole configuration form of this rules configuration. For rule
  11. * elements that are part of a configuration this method just adds the
  12. * elements configuration form.
  13. *
  14. * @param $form
  15. * The form array where to add the form.
  16. * @param $form_state
  17. * The current form state.
  18. * @param $options
  19. * An optional array of options with the known keys:
  20. * - 'show settings': Whether to include the 'settings' fieldset for
  21. * editing configuration settings like the label or categories. Defaults
  22. * to FALSE.
  23. * - 'button': Whether a submit button should be added. Defaults to FALSE.
  24. * - 'init': Whether the element is about to be configured the first time
  25. * and the configuration is about to be initialized. Defaults to FALSE.
  26. * - 'restrict plugins: May be used to restrict the list of rules plugins
  27. * that may be added to this configuration. For that set an array of
  28. * valid plugins. Note that conditions and actions are always valid, so
  29. * just set an empty array for just allowing those.
  30. * - 'restrict conditions': Optionally set an array of condition names to
  31. * restrict the conditions that are available for adding.
  32. * - 'restrict actions': Optionally set an array of action names to
  33. * restrict the actions that are available to for adding.
  34. * - 'restrict events': Optionally set an array of event names to restrict
  35. * the events that are available for adding.
  36. *
  37. * @todo
  38. * Implement the 'restrict *' options.
  39. */
  40. public function form(&$form, &$form_state, $options = array());
  41. /**
  42. * Validate the configuration form of this rule element.
  43. *
  44. * @param $form
  45. * @param $form_state
  46. */
  47. public function form_validate($form, &$form_state);
  48. /**
  49. * Submit the configuration form of this rule element. This makes sure to
  50. * put the updated configuration in the form state. For saving changes
  51. * permanently, just call $config->save() afterwards.
  52. *
  53. * @param $form
  54. * @param $form_state
  55. */
  56. public function form_submit($form, &$form_state);
  57. /**
  58. * Returns a structured array for rendering this element in overviews.
  59. */
  60. public function buildContent();
  61. /**
  62. * Returns the help text for editing this plugin.
  63. */
  64. public function help();
  65. /**
  66. * Returns ui operations for this element.
  67. */
  68. public function operations();
  69. }
  70. /**
  71. * Helper object for mapping elements to ids.
  72. */
  73. class RulesElementMap {
  74. /**
  75. * @var RulesPlugin
  76. */
  77. protected $configuration;
  78. protected $index = array();
  79. protected $counter = 0;
  80. public function __construct(RulesPlugin $config) {
  81. $this->configuration = $config->root();
  82. }
  83. /**
  84. * Makes sure each element has an assigned id.
  85. */
  86. public function index() {
  87. foreach ($this->getUnIndexedElements($this->configuration) as $element) {
  88. $id = &$element->property('elementId');
  89. $id = ++$this->counter;
  90. $this->index[$id] = $element;
  91. }
  92. }
  93. protected function getUnIndexedElements($element, &$unindexed = array()) {
  94. // Remember unindexed elements.
  95. $id = $element->property('elementId');
  96. if (!isset($id)) {
  97. $unindexed[] = $element;
  98. }
  99. else {
  100. // Make sure $this->counter refers to the highest id.
  101. if ($id > $this->counter) {
  102. $this->counter = $id;
  103. }
  104. $this->index[$id] = $element;
  105. }
  106. // Recurse down the tree.
  107. if ($element instanceof RulesContainerPlugin) {
  108. foreach ($element as $child) {
  109. $this->getUnIndexedElements($child, $unindexed);
  110. }
  111. }
  112. return $unindexed;
  113. }
  114. /**
  115. * Looks up the element with the given id.
  116. */
  117. public function lookup($id) {
  118. if (!$this->index) {
  119. $this->index();
  120. }
  121. return isset($this->index[$id]) ? $this->index[$id] : FALSE;
  122. }
  123. }
  124. /**
  125. * Faces UI extender for all kind of Rules plugins. Provides various useful
  126. * methods for any rules UI.
  127. */
  128. class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface {
  129. /**
  130. * @var RulesPlugin
  131. */
  132. protected $element;
  133. /**
  134. * The base path determines where a Rules overview UI lives. All forms that
  135. * want to display Rules (overview) forms need to set this variable. This is
  136. * necessary in order to get correct operation links, paths, redirects, bread
  137. * crumbs etc. for the form() and overviewTable() methods.
  138. *
  139. * @see RulesUIController
  140. * @see rules_admin_reaction_overview()
  141. * @see rules_admin_components_overview()
  142. */
  143. public static $basePath = NULL;
  144. /**
  145. * Provide $this->element to make the code more meaningful.
  146. */
  147. public function __construct(FacesExtendable $object) {
  148. parent::__construct($object);
  149. $this->element = $object;
  150. }
  151. /**
  152. * Returns the form values for the given form, possible being only a part of the whole form.
  153. *
  154. * In case the form is embedded somewhere, this function figures out the
  155. * location of its form values and returns them for further use.
  156. *
  157. * @param $form
  158. * A form array, or an array of form elements to get the value for.
  159. * @param $form_state
  160. * The form state as usual.
  161. */
  162. public static function &getFormStateValues($form, &$form_state) {
  163. $values = NULL;
  164. if (isset($form_state['values'])) {
  165. // Assume the top level if parents are not yet set.
  166. $form += array('#parents' => array());
  167. $values = &$form_state['values'];
  168. foreach ($form['#parents'] as $parent) {
  169. $values = &$values[$parent];
  170. }
  171. }
  172. return $values;
  173. }
  174. /**
  175. * Implements RulesPluginUIInterface.
  176. * Generates the element edit form.
  177. *
  178. * Note: Make sure that you set RulesPluginUI::$basePath before using this
  179. * method, otherwise paths, links, redirects etc. won't be correct.
  180. */
  181. public function form(&$form, &$form_state, $options = array()) {
  182. self::formDefaults($form, $form_state);
  183. $form_state += array('rules_element' => $this->element);
  184. // Add the help to the top of the form.
  185. $help = $this->element->help();
  186. $form['help'] = is_array($help) ? $help : array('#markup' => $help);
  187. // We use $form_state['element_settings'] to store the settings of both
  188. // parameter modes. That way one can switch between the parameter modes
  189. // without losing the settings of those.
  190. $form_state += array('element_settings' => $this->element->settings);
  191. $settings = $this->element->settings + $form_state['element_settings'];
  192. $form['parameter'] = array(
  193. '#tree' => TRUE,
  194. );
  195. foreach ($this->element->pluginParameterInfo() as $name => $parameter) {
  196. if ($parameter['type'] == 'hidden') {
  197. continue;
  198. }
  199. $form['parameter'][$name] = array(
  200. '#type' => 'fieldset',
  201. '#title' => check_plain($parameter['label']),
  202. '#description' => filter_xss(isset($parameter['description']) ? $parameter['description'] : ''),
  203. );
  204. // Init the parameter input mode.
  205. $form_state['parameter_mode'][$name] = !isset($form_state['parameter_mode'][$name]) ? NULL : $form_state['parameter_mode'][$name];
  206. $form['parameter'][$name] += $this->getParameterForm($name, $parameter, $settings, $form_state['parameter_mode'][$name]);
  207. }
  208. // Provide a form for editing the label and name of provided variables.
  209. $settings = $this->element->settings;
  210. foreach ($this->element->pluginProvidesVariables() as $var_name => $var_info) {
  211. $form['provides'][$var_name] = array(
  212. '#type' => 'fieldset',
  213. '#title' => check_plain($var_info['label']),
  214. '#description' => filter_xss(isset($var_info['description']) ? $var_info['description'] : ''),
  215. );
  216. $form['provides'][$var_name]['label'] = array(
  217. '#type' => 'textfield',
  218. '#title' => t('Variable label'),
  219. '#default_value' => isset($settings[$var_name . ':label']) ? $settings[$var_name . ':label'] : $var_info['label'],
  220. '#required' => TRUE,
  221. );
  222. $form['provides'][$var_name]['var'] = array(
  223. '#type' => 'textfield',
  224. '#title' => t('Variable name'),
  225. '#default_value' => isset($settings[$var_name . ':var']) ? $settings[$var_name . ':var'] : $var_name,
  226. '#description' => t('The variable name must contain only lowercase letters, numbers, and underscores and must be unique in the current scope.'),
  227. '#element_validate' => array('rules_ui_element_machine_name_validate'),
  228. '#required' => TRUE,
  229. );
  230. }
  231. if (!empty($form['provides'])) {
  232. $help = '<div class="description">' . t('Adjust the names and labels of provided variables, but note that renaming of already utilizied variables invalidates the existing uses.') . '</div>';
  233. $form['provides'] += array(
  234. '#tree' => TRUE,
  235. '#prefix' => '<h4 class="rules-form-heading">' . t('Provided variables') . '</h4>' . $help,
  236. );
  237. }
  238. // Add settings form, if specified.
  239. if (!empty($options['show settings'])) {
  240. $this->settingsForm($form, $form_state);
  241. }
  242. // Add submit button, if specified.
  243. if (!empty($options['button'])) {
  244. $form['submit'] = array(
  245. '#type' => 'submit',
  246. '#value' => t('Save'),
  247. '#weight' => 10,
  248. );
  249. }
  250. }
  251. /**
  252. * Actually generates the parameter form for the given data type.
  253. */
  254. protected function getParameterForm($name, $info, $settings, &$mode) {
  255. $class = $this->getDataTypeClass($info['type'], $info);
  256. $supports_input_mode = in_array('RulesDataDirectInputFormInterface', class_implements($class));
  257. // Init the mode.
  258. if (!isset($mode)) {
  259. if (isset($settings[$name . ':select'])) {
  260. $mode = 'selector';
  261. }
  262. elseif (isset($settings[$name]) && $supports_input_mode) {
  263. $mode = 'input';
  264. }
  265. elseif (isset($info['restriction'])) {
  266. $mode = $info['restriction'];
  267. }
  268. else {
  269. // Allow the parameter to define the 'default mode' and fallback to the
  270. // data type default.
  271. $mode = !empty($info['default mode']) ? $info['default mode'] : call_user_func(array($class, 'getDefaultMode'));
  272. }
  273. }
  274. // For translatable parameters, pre-populate an internal translation source
  275. // key so data type forms or input evaluators (i18n) may produce suiting
  276. // help.
  277. if (drupal_multilingual() && !empty($info['translatable'])) {
  278. $parameter = $this->element->pluginParameterInfo();
  279. $info['custom translation language'] = !empty($parameter['language']);
  280. }
  281. // Add the parameter form.
  282. if ($mode == 'input' && $supports_input_mode) {
  283. $form['settings'] = call_user_func(array($class, 'inputForm'), $name, $info, $settings, $this->element);
  284. }
  285. else {
  286. $form['settings'] = call_user_func(array($class, 'selectionForm'), $name, $info, $settings, $this->element);
  287. }
  288. // Add a link for switching the input mode when JS is enabled and a button
  289. // to switch it without javascript, in case switching is possible.
  290. if ($supports_input_mode && empty($info['restriction'])) {
  291. $value = $mode == 'selector' ? t('Switch to the direct input mode') : t('Switch to data selection');
  292. $form['switch_button'] = array(
  293. '#type' => 'submit',
  294. '#name' => 'param_' . $name,
  295. '#attributes' => array('class' => array('rules-switch-button')),
  296. '#parameter' => $name,
  297. '#value' => $value,
  298. '#submit' => array('rules_ui_parameter_replace_submit'),
  299. '#ajax' => rules_ui_form_default_ajax('none'),
  300. // Do not validate!
  301. '#limit_validation_errors' => array(),
  302. );
  303. }
  304. return $form;
  305. }
  306. /**
  307. * Implements RulesPluginUIInterface.
  308. */
  309. public function form_validate($form, &$form_state) {
  310. $this->form_extract_values($form, $form_state);
  311. $form_values = RulesPluginUI::getFormStateValues($form, $form_state);
  312. if (isset($form_values['provides'])) {
  313. $vars = $this->element->availableVariables();
  314. foreach ($form_values['provides'] as $name => $values) {
  315. if (isset($vars[$values['var']])) {
  316. form_error($form['provides'][$name]['var'], t('The variable name %name is already taken.', array('%name' => $values['var'])));
  317. }
  318. }
  319. }
  320. // Settings have been updated, so process them now.
  321. $this->element->processSettings(TRUE);
  322. // Make sure the current user really has access to configure this element
  323. // as well as the used input evaluators and data processors.
  324. if (!user_access('bypass rules access') && !$this->element->root()->access()) {
  325. form_set_error('', t('Access violation! You have insufficient access permissions to edit this configuration.'));
  326. }
  327. if (!empty($form['settings'])) {
  328. $this->settingsFormValidate($form, $form_state);
  329. }
  330. }
  331. /**
  332. * Applies the values of the form to the element.
  333. */
  334. public function form_extract_values($form, &$form_state) {
  335. $this->element->settings = array();
  336. $form_values = RulesPluginUI::getFormStateValues($form, $form_state);
  337. if (isset($form_values['parameter'])) {
  338. foreach ($form_values['parameter'] as $name => $values) {
  339. $this->element->settings += $values['settings'];
  340. }
  341. }
  342. if (isset($form_values['provides'])) {
  343. foreach ($form_values['provides'] as $name => $values) {
  344. $this->element->settings[$name . ':label'] = $values['label'];
  345. $this->element->settings[$name . ':var'] = $values['var'];
  346. }
  347. }
  348. if (!empty($form['settings'])) {
  349. $this->settingsFormExtractValues($form, $form_state);
  350. }
  351. }
  352. /**
  353. * Implements RulesPluginUIInterface.
  354. */
  355. public function form_submit($form, &$form_state) {
  356. if (!empty($form['settings'])) {
  357. $this->settingsFormSubmit($form, $form_state);
  358. }
  359. $this->element->save();
  360. }
  361. /**
  362. * Adds the configuration settings form (label, tags, description, ..).
  363. */
  364. public function settingsForm(&$form, &$form_state) {
  365. $form_values = RulesPluginUI::getFormStateValues($form, $form_state);
  366. // Add the settings in a separate fieldset below.
  367. $form['settings'] = array(
  368. '#type' => 'fieldset',
  369. '#title' => t('Settings'),
  370. '#collapsible' => TRUE,
  371. '#collapsed' => empty($form_values['settings']['vars']['more']),
  372. '#weight' => 5,
  373. '#tree' => TRUE,
  374. );
  375. $form['settings']['label'] = array(
  376. '#type' => 'textfield',
  377. '#title' => t('Name'),
  378. '#default_value' => $this->element->label(),
  379. '#required' => TRUE,
  380. '#weight' => -5,
  381. );
  382. if (!empty($this->element->module) && !empty($this->element->name) && $this->element->module == 'rules' && strpos($this->element->name, 'rules_') === 0) {
  383. // Remove the Rules module prefix from the machine name.
  384. $machine_name = substr($this->element->name, strlen($this->element->module) + 1);
  385. }
  386. else {
  387. $machine_name = $this->element->name;
  388. }
  389. $form['settings']['name'] = array(
  390. '#type' => 'machine_name',
  391. '#default_value' => isset($machine_name) ? $machine_name : '',
  392. // The string 'rules_' is pre-pended to machine names, so the
  393. // maxlength must be less than the field length of 64 characters.
  394. '#maxlength' => 58,
  395. '#disabled' => entity_has_status('rules_config', $this->element, ENTITY_IN_CODE) && !(isset($form_state['op']) && $form_state['op'] == 'clone'),
  396. '#machine_name' => array(
  397. 'exists' => 'rules_config_load',
  398. 'source' => array('settings', 'label'),
  399. ),
  400. '#required' => TRUE,
  401. '#description' => t('The machine-readable name of this configuration is used by rules internally to identify the configuration. This name must contain only lowercase letters, numbers, and underscores and must be unique.'),
  402. );
  403. $form['settings']['tags'] = array(
  404. '#type' => 'textfield',
  405. '#title' => t('Tags'),
  406. '#default_value' => isset($this->element->tags) ? drupal_implode_tags($this->element->tags) : '',
  407. '#autocomplete_path' => 'admin/config/workflow/rules/autocomplete_tags',
  408. '#description' => t('Tags associated with this configuration, used for filtering in the admin interface. Separate multiple tags with commas.'),
  409. );
  410. // Show a form for editing variables for components.
  411. if (($plugin_info = $this->element->pluginInfo()) && !empty($plugin_info['component'])) {
  412. if ($this->element->hasStatus(ENTITY_IN_CODE)) {
  413. $description = t('The variables used by the component. They can not be edited for configurations that are provided in code.');
  414. }
  415. else {
  416. $description = t('Variables are normally input <em>parameters</em> for the component – data that should be available for the component to act on. Additionaly, action components may <em>provide</em> variables back to the caller. Each variable must have a specified data type, a label and a unique machine readable name containing only lowercase alphanumeric characters and underscores. See <a href="@url">the online documentation</a> for more information about variables.',
  417. array('@url' => rules_external_help('variables'))
  418. );
  419. }
  420. $form['settings']['vars'] = array(
  421. '#prefix' => '<div id="rules-component-variables">',
  422. '#suffix' => '</div>',
  423. '#tree' => TRUE,
  424. '#element_validate' => array('rules_ui_element_variable_form_validate'),
  425. '#theme' => 'rules_ui_variable_form',
  426. '#title' => t('Variables'),
  427. '#description' => $description,
  428. // Variables can not be edited on configurations in code.
  429. '#disabled' => $this->element->hasStatus(ENTITY_IN_CODE),
  430. );
  431. $weight = 0;
  432. $provides = $this->element->providesVariables();
  433. foreach ($this->element->componentVariables() as $name => $var_info) {
  434. $form['settings']['vars']['items'][$name] = RulesPluginUI::getVariableForm($name, $var_info, isset($provides[$name]));
  435. $form['settings']['vars']['items'][$name]['weight']['#default_value'] = $weight++;
  436. }
  437. // Always add three empty forms.
  438. for ($i = 0; $i < 3; $i++) {
  439. $form['settings']['vars']['items'][$i] = RulesPluginUI::getVariableForm();
  440. $form['settings']['vars']['items'][$i]['weight']['#default_value'] = $weight++;
  441. }
  442. $form['settings']['vars']['more'] = array(
  443. '#type' => 'submit',
  444. '#value' => t('Add more'),
  445. // Enable AJAX once #756762 is fixed.
  446. // '#ajax' => rules_ui_form_default_ajax('none'),
  447. '#limit_validation_errors' => array(array('vars')),
  448. '#submit' => array('rules_form_submit_rebuild'),
  449. );
  450. if (!empty($this->element->id)) {
  451. // Display a setting to manage access.
  452. $form['settings']['access'] = array(
  453. '#weight' => 50,
  454. );
  455. $plugin_type = $this->element instanceof RulesActionInterface ? t('action') : t('condition');
  456. $form['settings']['access']['access_exposed'] = array(
  457. '#type' => 'checkbox',
  458. '#title' => t('Configure access for using this component with a permission.'),
  459. '#default_value' => !empty($this->element->access_exposed),
  460. '#description' => t('By default, the @plugin-type for using this component may be only used by users that have access to configure the component. If checked, access is determined by a permission instead.', array('@plugin-type' => $plugin_type))
  461. );
  462. $form['settings']['access']['permissions'] = array(
  463. '#type' => 'container',
  464. '#states' => array(
  465. 'visible' => array(
  466. ':input[name="settings[access][access_exposed]"]' => array('checked' => TRUE),
  467. ),
  468. ),
  469. );
  470. $form['settings']['access']['permissions']['matrix'] = $this->settingsFormPermissionMatrix();
  471. }
  472. }
  473. // TODO: Attach field form thus description.
  474. }
  475. /**
  476. * Provides a matrix permission for the component based in the existing roles.
  477. *
  478. * @return
  479. * Form elements with the matrix of permissions for a component.
  480. */
  481. protected function settingsFormPermissionMatrix() {
  482. $form['#theme'] = 'user_admin_permissions';
  483. $status = array();
  484. $options = array();
  485. $role_names = user_roles();
  486. $role_permissions = user_role_permissions($role_names);
  487. $component_permission = rules_permissions_by_component(array($this->element));
  488. $component_permission_name = key($component_permission);
  489. $form['permission'][$component_permission_name] = array(
  490. '#type' => 'item',
  491. '#markup' => $component_permission[$component_permission_name]['title'],
  492. );
  493. $options[$component_permission_name] = '';
  494. foreach ($role_names as $rid => $name) {
  495. if (isset($role_permissions[$rid][$component_permission_name])) {
  496. $status[$rid][] = $component_permission_name;
  497. }
  498. }
  499. // Build the checkboxes for each role.
  500. foreach ($role_names as $rid => $name) {
  501. $form['checkboxes'][$rid] = array(
  502. '#type' => 'checkboxes',
  503. '#options' => $options,
  504. '#default_value' => isset($status[$rid]) ? $status[$rid] : array(),
  505. '#attributes' => array('class' => array('rid-' . $rid)),
  506. );
  507. $form['role_names'][$rid] = array('#markup' => check_plain($name), '#tree' => TRUE);
  508. }
  509. // Attach the default permissions page JavaScript.
  510. $form['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.permissions.js';
  511. return $form;
  512. }
  513. public function settingsFormExtractValues($form, &$form_state) {
  514. $form_values = RulesPluginUI::getFormStateValues($form['settings'], $form_state);
  515. $this->element->label = $form_values['label'];
  516. // If the name was changed we have to redirect to the URL that contains
  517. // the new name, instead of rebuilding on the old URL with the old name
  518. if ($form['settings']['name']['#default_value'] != $form_values['name']) {
  519. $module = isset($this->element->module) ? $this->element->module : 'rules';
  520. $this->element->name = $module . '_' . $form_values['name'];
  521. $form_state['redirect'] = RulesPluginUI::path($this->element->name);
  522. }
  523. $this->element->tags = empty($form_values['tags']) ? array() : drupal_explode_tags($form_values['tags']);
  524. if (isset($form_values['vars']['items'])) {
  525. $vars = &$this->element->componentVariables();
  526. $vars = array();
  527. if ($this->element instanceof RulesActionContainer) {
  528. $provides = &$this->element->componentProvidesVariables();
  529. $provides = array();
  530. }
  531. usort($form_values['vars']['items'], 'rules_element_sort_helper');
  532. foreach ($form_values['vars']['items'] as $item) {
  533. if ($item['type'] && $item['name'] && $item['label']) {
  534. $vars[$item['name']] = array('label' => $item['label'], 'type' => $item['type']);
  535. if (!$item['usage'][0]) {
  536. $vars[$item['name']]['parameter'] = FALSE;
  537. }
  538. if ($item['usage'][1] && isset($provides)) {
  539. $provides[] = $item['name'];
  540. }
  541. }
  542. }
  543. // Disable FAPI persistence for the variable form so renumbering works.
  544. $input = &$form_state['input'];
  545. foreach ($form['settings']['#parents'] as $parent) {
  546. $input = &$input[$parent];
  547. }
  548. unset($input['vars']);
  549. }
  550. $this->element->access_exposed = isset($form_values['access']['access_exposed']) ? $form_values['access']['access_exposed'] : FALSE;
  551. }
  552. public function settingsFormValidate($form, &$form_state) {
  553. $form_values = RulesPluginUI::getFormStateValues($form['settings'], $form_state);
  554. if ($form['settings']['name']['#default_value'] != $form_values['name'] && rules_config_load($this->element->name)) {
  555. form_error($form['settings']['name'], t('The machine-readable name %name is already taken.', array('%name' => $form_values['name'])));
  556. }
  557. }
  558. public function settingsFormSubmit($form, &$form_state) {
  559. if (isset($form_state['values']['settings']['access']) && !empty($this->element->access_exposed)) {
  560. // Save the permission matrix.
  561. foreach ($form_state['values']['settings']['access']['permissions']['matrix']['checkboxes'] as $rid => $value) {
  562. user_role_change_permissions($rid, $value);
  563. }
  564. }
  565. }
  566. /**
  567. * Returns the form for configuring the info of a single variable.
  568. */
  569. public function getVariableForm($name = '', $info = array(), $provided = FALSE) {
  570. $form['type'] = array(
  571. '#type' => 'select',
  572. '#options' => array(0 => '--') + RulesPluginUI::getOptions('data'),
  573. '#default_value' => isset($info['type']) ? $info['type'] : 0,
  574. );
  575. $form['label'] = array(
  576. '#type' => 'textfield',
  577. '#size' => 40,
  578. '#default_value' => isset($info['label']) ? $info['label'] : '',
  579. );
  580. $form['name'] = array(
  581. '#type' => 'textfield',
  582. '#size' => 40,
  583. '#default_value' => $name,
  584. '#element_validate' => array('rules_ui_element_machine_name_validate'),
  585. );
  586. $usage[0] = !isset($info['parameter']) || $info['parameter'] ? 1 : 0;
  587. $usage[1] = $provided ? 1 : 0;
  588. $form['usage'] = array(
  589. '#type' => 'select',
  590. '#default_value' => implode('', $usage),
  591. '#options' => array(
  592. '10' => t('Parameter'),
  593. '11' => t('Parameter + Provided'),
  594. '01' => t('Provided'),
  595. ),
  596. );
  597. if ($this->element instanceof RulesConditionContainer) {
  598. $form['usage']['#disabled'] = TRUE;
  599. }
  600. // Just set the weight #default_value for the returned form.
  601. $form['weight'] = array(
  602. '#type' => 'weight',
  603. );
  604. return $form;
  605. }
  606. /**
  607. * Returns the name of class for the given data type.
  608. *
  609. * @param $data_type
  610. * The name of the data typ
  611. * @param $parameter_info
  612. * (optional) An array of info about the to be configured parameter.
  613. */
  614. public function getDataTypeClass($data_type, $parameter_info = array()) {
  615. if (!empty($parameter_info['ui class'])) {
  616. return $parameter_info['ui class'];
  617. }
  618. $cache = rules_get_cache();
  619. $data_info = $cache['data_info'];
  620. return (is_string($data_type) && isset($data_info[$data_type]['ui class'])) ? $data_info[$data_type]['ui class'] : 'RulesDataUI';
  621. }
  622. /**
  623. * Implements RulesPluginUIInterface.
  624. * Show a preview of the configuration settings.
  625. */
  626. public function buildContent() {
  627. $config_name = $this->element->root()->name;
  628. $content['label'] = array(
  629. '#type' => 'link',
  630. '#title' => $this->element->label(),
  631. '#href' => $this->element->isRoot() ? RulesPluginUI::path($config_name) : RulesPluginUI::path($config_name, 'edit', $this->element),
  632. '#prefix' => '<div class="rules-element-label">',
  633. '#suffix' => '</div>'
  634. );
  635. // Put the elements below in a "description" div.
  636. $content['description'] = array(
  637. '#prefix' => '<div class="description">',
  638. );
  639. $content['description']['parameter'] = array(
  640. '#caption' => t('Parameter'),
  641. '#theme' => 'rules_content_group',
  642. );
  643. foreach ($this->element->pluginParameterInfo() as $name => $parameter) {
  644. $element = array();
  645. if (!empty($this->element->settings[$name . ':select'])) {
  646. $element['content'] = array(
  647. '#markup' => '[' . $this->element->settings[$name . ':select'] . ']',
  648. );
  649. }
  650. elseif (isset($this->element->settings[$name]) && (!isset($parameter['default value']) || $parameter['default value'] != $this->element->settings[$name])) {
  651. $method = empty($parameter['options list']) ? 'render' : 'renderOptionsLabel';
  652. $class = $this->getDataTypeClass($parameter['type'], $parameter);
  653. // We cannot use method_exists() here as it would trigger a PHP bug,
  654. // @see http://drupal.org/node/1258284
  655. $element = call_user_func(array($class, $method), $this->element->settings[$name], $name, $parameter, $this->element);
  656. }
  657. // Only add parameters that are really configured / not default.
  658. if ($element) {
  659. $content['description']['parameter'][$name] = array(
  660. '#theme' => 'rules_parameter_configuration',
  661. '#info' => $parameter,
  662. ) + $element;
  663. }
  664. }
  665. foreach ($this->element->providesVariables() as $name => $var_info) {
  666. $content['description']['provides'][$name] = array(
  667. '#theme' => 'rules_variable_view',
  668. '#info' => $var_info,
  669. '#name' => $name,
  670. );
  671. }
  672. if (!empty($content['description']['provides'])) {
  673. $content['description']['provides'] += array(
  674. '#caption' => t('Provides variables'),
  675. '#theme' => 'rules_content_group',
  676. );
  677. }
  678. // Add integrity exception messages if there are any for this element.
  679. try {
  680. $this->element->integrityCheck();
  681. // A configuration is still marked as dirty, but already works again.
  682. if (!empty($this->element->dirty)) {
  683. rules_config_update_dirty_flag($this->element);
  684. $variables = array('%label' => $this->element->label(), '%name' => $this->element->name, '@plugin' => $this->element->plugin());
  685. drupal_set_message(t('The @plugin %label (%name) was marked dirty, but passes the integrity check now and is active again.', $variables));
  686. rules_clear_cache();
  687. }
  688. }
  689. catch (RulesIntegrityException $e) {
  690. $content['description']['integrity'] = array(
  691. '#theme' => 'rules_content_group',
  692. '#caption' => t('Error'),
  693. '#attributes' => array('class' => array('rules-content-group-integrity-error')),
  694. 'error' => array(
  695. '#markup' => filter_xss($e->getMessage()),
  696. ),
  697. );
  698. // Also make sure the rule is marked as dirty.
  699. if (empty($this->element->dirty)) {
  700. rules_config_update_dirty_flag($this->element);
  701. rules_clear_cache();
  702. }
  703. }
  704. $content['#suffix'] = '</div>';
  705. $content['#type'] = 'container';
  706. $content['#attributes']['class'][] = 'rules-element-content';
  707. return $content;
  708. }
  709. /**
  710. * Implements RulesPluginUIInterface.
  711. */
  712. public function operations() {
  713. $name = $this->element->root()->name;
  714. $render = array(
  715. '#theme' => 'links__rules',
  716. );
  717. $render['#attributes']['class'][] = 'rules-operations';
  718. $render['#attributes']['class'][] = 'action-links';
  719. $render['#links']['edit'] = array(
  720. 'title' => t('edit'),
  721. 'href' => RulesPluginUI::path($name, 'edit', $this->element),
  722. );
  723. $render['#links']['delete'] = array(
  724. 'title' => t('delete'),
  725. 'href' => RulesPluginUI::path($name, 'delete', $this->element),
  726. );
  727. return $render;
  728. }
  729. /**
  730. * Implements RulesPluginUIInterface.
  731. */
  732. public function help() {}
  733. /**
  734. * Deprecated by the controllers overviewTable() method.
  735. */
  736. public static function overviewTable($conditions = array(), $options = array()) {
  737. return rules_ui()->overviewTable($conditions, $options);
  738. }
  739. /**
  740. * Generates a path using the given operation for the element with the given
  741. * id of the configuration with the given name.
  742. */
  743. public static function path($name, $op = NULL, RulesPlugin $element = NULL, $parameter = FALSE) {
  744. $element_id = isset($element) ? $element->elementId() : FALSE;
  745. if (isset(self::$basePath)) {
  746. $base_path = self::$basePath;
  747. }
  748. // Default to the paths used by 'rules_admin', so modules can easily re-use
  749. // its UI.
  750. else {
  751. $base_path = isset($element) && $element instanceof RulesTriggerableInterface ? 'admin/config/workflow/rules/reaction' : 'admin/config/workflow/rules/components';
  752. }
  753. return implode('/', array_filter(array($base_path . '/manage', $name, $op, $element_id, $parameter)));
  754. }
  755. /**
  756. * Determines the default redirect target for an edited/deleted element. This
  757. * is a parent element which is either a rule or the configuration root.
  758. */
  759. public static function defaultRedirect(RulesPlugin $element) {
  760. while (!$element->isRoot()) {
  761. if ($element instanceof Rule) {
  762. return self::path($element->root()->name, 'edit', $element);
  763. }
  764. $element = $element->parentElement();
  765. }
  766. return self::path($element->name);
  767. }
  768. /**
  769. * Returns an array of options to use with a select for the items specified
  770. * in the given hook.
  771. *
  772. * @param $item_type
  773. * The item type to get options for. One of 'data', 'event', 'condition' and
  774. * 'action'.
  775. * @param $items
  776. * (optional) An array of items to restrict the options to.
  777. *
  778. * @return
  779. * An array of options.
  780. */
  781. public static function getOptions($item_type, $items = NULL) {
  782. $sorted_data = array();
  783. $ungrouped = array();
  784. $data = $items ? $items : rules_fetch_data($item_type . '_info');
  785. foreach ($data as $name => $info) {
  786. // Verfiy the current user has access to use it.
  787. if (!user_access('bypass rules access') && !empty($info['access callback']) && !call_user_func($info['access callback'], $item_type, $name)) {
  788. continue;
  789. }
  790. if (!empty($info['group'])) {
  791. $sorted_data[drupal_ucfirst($info['group'])][$name] = drupal_ucfirst($info['label']);
  792. }
  793. else {
  794. $ungrouped[$name] = drupal_ucfirst($info['label']);
  795. }
  796. }
  797. asort($ungrouped);
  798. foreach ($sorted_data as $key => $choices) {
  799. asort($choices);
  800. $sorted_data[$key] = $choices;
  801. }
  802. ksort($sorted_data);
  803. // Always move the 'Components' group down it it exists.
  804. if (isset($sorted_data[t('Components')])) {
  805. $copy = $sorted_data[t('Components')];
  806. unset($sorted_data[t('Components')]);
  807. $sorted_data[t('Components')] = $copy;
  808. }
  809. return $ungrouped + $sorted_data;
  810. }
  811. public static function formDefaults(&$form, &$form_state) {
  812. form_load_include($form_state, 'inc', 'rules', 'ui/ui.forms');
  813. // Add our own css.
  814. $form['#attached']['css'][] = drupal_get_path('module', 'rules') . '/ui/rules.ui.css';
  815. // Workaround for problems with jquery css in seven theme and the core
  816. // autocomplete.
  817. if ($GLOBALS['theme'] == 'seven') {
  818. $form['#attached']['css'][] = drupal_get_path('module', 'rules') . '/ui/rules.ui.seven.css';
  819. }
  820. // Specify the wrapper div used by #ajax.
  821. $form['#prefix'] = '<div id="rules-form-wrapper">';
  822. $form['#suffix'] = '</div>';
  823. // Preserve the base path in the form state. The after build handler will
  824. // set self::$basePath again for cached forms.
  825. if (isset(self::$basePath)) {
  826. $form_state['_rules_base_path'] = RulesPluginUI::$basePath;
  827. $form['#after_build'][] = 'rules_form_after_build_restore_base_path';
  828. }
  829. }
  830. public static function getTags() {
  831. $result = db_select('rules_tags')
  832. ->distinct()
  833. ->fields('rules_tags', array('tag'))
  834. ->groupBy('tag')
  835. ->execute()
  836. ->fetchCol('tag');
  837. return drupal_map_assoc($result);
  838. }
  839. }
  840. /**
  841. * UI for abstract plugins (conditions & actions).
  842. */
  843. class RulesAbstractPluginUI extends RulesPluginUI {
  844. /**
  845. * Overridden to invoke the abstract plugins form alter callback and to add
  846. * the negation checkbox for conditions.
  847. */
  848. public function form(&$form, &$form_state, $options = array()) {
  849. parent::form($form, $form_state, $options);
  850. if ($this->element instanceof RulesCondition) {
  851. $form['negate'] = array(
  852. '#title' => t('Negate'),
  853. '#type' => 'checkbox',
  854. '#description' => t('If checked, the condition result is negated such that it returns TRUE if it evaluates to FALSE.'),
  855. '#default_value' => $this->element->isNegated(),
  856. '#weight' => 5,
  857. );
  858. }
  859. $this->element->call('form_alter', array(&$form, &$form_state, $options));
  860. }
  861. public function form_extract_values($form, &$form_state) {
  862. parent::form_extract_values($form, $form_state);
  863. $form_values = RulesPluginUI::getFormStateValues($form, $form_state);
  864. if ($this->element instanceof RulesCondition && isset($form_values['negate'])) {
  865. $this->element->negate($form_values['negate']);
  866. }
  867. }
  868. public function form_validate($form, &$form_state) {
  869. parent::form_validate($form, $form_state);
  870. // Validate the edited element and throw validation errors if it fails.
  871. try {
  872. $this->element->integrityCheck();
  873. }
  874. catch (RulesIntegrityException $e) {
  875. form_set_error(implode('][', $e->keys), $e->getMessage());
  876. }
  877. }
  878. }
  879. /**
  880. * UI for Rules Container.
  881. */
  882. class RulesContainerPluginUI extends RulesPluginUI {
  883. /**
  884. * Generates a table for editing the contained elements.
  885. */
  886. public function form(&$form, &$form_state, $options = array(), $iterator = NULL) {
  887. parent::form($form, $form_state, $options);
  888. $form['elements'] = array(
  889. // Hide during creation or for embedded elements.
  890. '#access' => empty($options['init']) && $this->element->isRoot(),
  891. '#tree' => TRUE,
  892. '#theme' => 'rules_elements',
  893. '#empty' => t('None'),
  894. '#caption' => t('Elements')
  895. );
  896. $form['elements']['#attributes']['class'][] = 'rules-container-plugin';
  897. // Recurse over all element childrens or use the provided iterator.
  898. $iterator = isset($iterator) ? $iterator : $this->element->elements();
  899. $root_depth = $this->element->depth();
  900. foreach ($iterator as $key => $child) {
  901. $id = $child->elementId();
  902. // Do not render rules as container element when displayed in a rule set.
  903. $is_container = $child instanceof RulesContainerPlugin && !($child instanceof Rule);
  904. $form['elements'][$id] = array(
  905. '#depth' => $child->depth() - $root_depth - 1,
  906. '#container' => $is_container,
  907. );
  908. $form['elements'][$id]['label'] = $child->buildContent();
  909. $form['elements'][$id]['weight'] = array(
  910. '#type' => 'weight',
  911. '#default_value' => $child->weight,
  912. '#delta' => 20,
  913. );
  914. $form['elements'][$id]['parent_id'] = array(
  915. '#type' => 'hidden',
  916. // If another iterator is passed in, the childs parent may not equal
  917. // the current element. Thus ask the child for its parent.
  918. '#default_value' => $child->parentElement()->elementId(),
  919. );
  920. $form['elements'][$id]['element_id'] = array(
  921. '#type' => 'hidden',
  922. '#default_value' => $id,
  923. );
  924. $form['elements'][$id]['operations'] = $child->operations();
  925. }
  926. // Alter the submit button label.
  927. if (!empty($options['button']) && !empty($options['init'])) {
  928. $form['submit']['#value'] = t('Continue');
  929. }
  930. elseif (!empty($options['button']) && $this->element->isRoot()) {
  931. $form['submit']['#value'] = t('Save changes');
  932. }
  933. }
  934. /**
  935. * Applies the values of the form to the given rule configuration.
  936. */
  937. public function form_extract_values($form, &$form_state) {
  938. parent::form_extract_values($form, $form_state);
  939. $values = RulesPluginUI::getFormStateValues($form, $form_state);
  940. // Now apply the new hierarchy.
  941. if (isset($values['elements'])) {
  942. foreach ($values['elements'] as $id => $data) {
  943. $child = $this->element->elementMap()->lookup($id);
  944. $child->weight = $data['weight'];
  945. $parent = $this->element->elementMap()->lookup($data['parent_id']);
  946. $child->setParent($parent ? $parent : $this->element);
  947. }
  948. $this->element->sortChildren(TRUE);
  949. }
  950. }
  951. public function operations() {
  952. $ops = parent::operations();
  953. $add_ops = self::addOperations();
  954. $ops['#links'] += $add_ops['#links'];
  955. return $ops;
  956. }
  957. /**
  958. * Gets the Add-* operations for the given element.
  959. */
  960. public function addOperations() {
  961. $name = $this->element->root()->name;
  962. $render = array(
  963. '#theme' => 'links__rules',
  964. );
  965. $render['#attributes']['class'][] = 'rules-operations-add';
  966. $render['#attributes']['class'][] = 'action-links';
  967. foreach (rules_fetch_data('plugin_info') as $plugin => $info) {
  968. if (!empty($info['embeddable']) && $this->element instanceof $info['embeddable']) {
  969. $render['#links']['add_' . $plugin] = array(
  970. 'title' => t('Add !name', array('!name' => $plugin)),
  971. 'href' => RulesPluginUI::path($name, 'add', $this->element, $plugin),
  972. );
  973. }
  974. }
  975. return $render;
  976. }
  977. public function buildContent() {
  978. $content = parent::buildContent();
  979. // Don't link the title for embedded container plugins, except for rules.
  980. if (!$this->element->isRoot() && !($this->element instanceof Rule)) {
  981. $content['label']['#type'] = 'markup';
  982. $content['label']['#markup'] = check_plain($content['label']['#title']);
  983. unset($content['label']['#title']);
  984. }
  985. elseif ($this->element->isRoot()) {
  986. $content['description']['settings'] = array(
  987. '#theme' => 'rules_content_group',
  988. '#weight' => -4,
  989. 'machine_name' => array(
  990. '#markup' => t('Machine name') . ': ' . $this->element->name,
  991. ),
  992. 'weight' => array(
  993. '#access' => $this->element instanceof RulesTriggerableInterface,
  994. '#markup' => t('Weight') . ': ' . $this->element->weight,
  995. ),
  996. );
  997. if (!empty($this->element->tags)) {
  998. $content['description']['tags'] = array(
  999. '#theme' => 'rules_content_group',
  1000. '#caption' => t('Tags'),
  1001. 'tags' => array(
  1002. '#markup' => check_plain(drupal_implode_tags($this->element->tags)),
  1003. ),
  1004. );
  1005. }
  1006. if ($vars = $this->element->componentVariables()) {
  1007. $content['description']['variables'] = array(
  1008. '#caption' => t('Parameter'),
  1009. '#theme' => 'rules_content_group',
  1010. );
  1011. foreach ($vars as $name => $info) {
  1012. if (!isset($info['parameter']) || $info['parameter']) {
  1013. $content['description']['variables'][$name] = array(
  1014. '#theme' => 'rules_variable_view',
  1015. '#info' => $info,
  1016. '#name' => $name,
  1017. );
  1018. }
  1019. }
  1020. }
  1021. }
  1022. return $content;
  1023. }
  1024. }
  1025. /**
  1026. * UI for Rules condition container.
  1027. */
  1028. class RulesConditionContainerUI extends RulesContainerPluginUI {
  1029. public function form(&$form, &$form_state, $options = array(), $iterator = NULL) {
  1030. parent::form($form, $form_state, $options, $iterator);
  1031. // Add the add-* operation links.
  1032. $form['elements']['#add'] = self::addOperations();
  1033. $form['elements']['#attributes']['class'][] = 'rules-condition-container';
  1034. $form['elements']['#caption'] = t('Conditions');
  1035. // By default skip
  1036. if (!empty($options['init']) && !$this->element->isRoot()) {
  1037. $config = $this->element->root();
  1038. $form['init_help'] = array(
  1039. '#type' => 'container',
  1040. '#id' => 'rules-plugin-add-help',
  1041. 'content' => array(
  1042. '#markup' => t('You are about to add a new @plugin to the @config-plugin %label. Use indentation to make conditions a part of this logic group. See <a href="@url">the online documentation</a> for more information on condition sets.',
  1043. array('@plugin' => $this->element->plugin(),
  1044. '@config-plugin' => $config->plugin(),
  1045. '%label' => $config->label(),
  1046. '@url' => rules_external_help('condition-components'))),
  1047. ),
  1048. );
  1049. }
  1050. $form['negate'] = array(
  1051. '#title' => t('Negate'),
  1052. '#type' => 'checkbox',
  1053. '#description' => t('If checked, the condition result is negated such that it returns TRUE if it evaluates to FALSE.'),
  1054. '#default_value' => $this->element->isNegated(),
  1055. '#weight' => 5,
  1056. );
  1057. }
  1058. public function form_extract_values($form, &$form_state) {
  1059. parent::form_extract_values($form, $form_state);
  1060. $form_values = RulesPluginUI::getFormStateValues($form, $form_state);
  1061. if (isset($form_values['negate'])) {
  1062. $this->element->negate($form_values['negate']);
  1063. }
  1064. }
  1065. }
  1066. /**
  1067. * UI for Rules action container.
  1068. */
  1069. class RulesActionContainerUI extends RulesContainerPluginUI {
  1070. public function form(&$form, &$form_state, $options = array(), $iterator = NULL) {
  1071. parent::form($form, $form_state, $options, $iterator);
  1072. // Add the add-* operation links.
  1073. $form['elements']['#add'] = self::addOperations();
  1074. $form['elements']['#attributes']['class'][] = 'rules-action-container';
  1075. $form['elements']['#caption'] = t('Actions');
  1076. }
  1077. }