ui.core.inc 47 KB

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