rules_link.module 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. <?php
  2. /**
  3. * @file
  4. * Rules Link - module file.
  5. */
  6. /**
  7. * Implements hook_entity_info().
  8. */
  9. function rules_link_entity_info() {
  10. return array(
  11. 'rules_link' => array(
  12. 'label' => t('Rules Link'),
  13. 'entity class' => 'RulesLink',
  14. 'controller class' => 'EntityAPIControllerExportable',
  15. 'base table' => 'rules_link',
  16. 'fieldable' => TRUE,
  17. 'entity keys' => array(
  18. 'id' => 'id',
  19. 'name' => 'name',
  20. 'label' => 'label',
  21. ),
  22. 'exportable' => TRUE,
  23. 'module' => 'rules_link',
  24. 'access callback' => 'rules_link_access',
  25. // Enable the entity API's admin UI.
  26. 'admin ui' => array(
  27. 'path' => 'admin/config/workflow/rules_links',
  28. 'file' => 'rules_link.admin.inc',
  29. 'controller class' => 'RulesLinkUIController',
  30. ),
  31. ),
  32. );
  33. }
  34. /**
  35. * Access callback for the entity API.
  36. */
  37. function rules_link_access($op, $type = NULL, $account = NULL) {
  38. return user_access('administer rules links', $account);
  39. }
  40. /**
  41. * Menu argument loader; Load a rules link by string.
  42. *
  43. * @param $name
  44. * The machine-readable name of a rules link to load.
  45. * @return
  46. * A rules link array or FALSE if $name does not exist.
  47. */
  48. function rules_link_load($name) {
  49. return rules_link_get_links($name);
  50. }
  51. /**
  52. * Implements hook_permission().
  53. */
  54. function rules_link_permission() {
  55. $permissions = array(
  56. 'administer rules links' => array(
  57. 'title' => t('Administer rules links'),
  58. 'description' => t('Create and delete rules links and set their permissions.'),
  59. ),
  60. );
  61. foreach (rules_link_get_links() as $link) {
  62. $name = check_plain($link->name);
  63. $permissions += array(
  64. "access rules link $name" => array(
  65. 'title' => t('%link_label: Execute rules link', array('%link_label' => $link->label)),
  66. ),
  67. );
  68. }
  69. return $permissions;
  70. }
  71. /**
  72. * Gets an array of all rules links, keyed by the type name.
  73. *
  74. * @param $link_names
  75. * If set, the function will return the link with the given name.
  76. * @return RuleLinks[]
  77. * Depending on isset $link_names, a name single or an array of rules links.
  78. */
  79. function rules_link_get_links($link_names = NULL) {
  80. $links = entity_load_multiple_by_name('rules_link', isset($link_names) ? array($link_names) : FALSE);
  81. return isset($link_names) ? reset($links) : $links;
  82. }
  83. /**
  84. * Returns the name condition set to a rules link.
  85. */
  86. function rules_link_get_condition_set_name($rules_link) {
  87. return 'rules_link_condition_' . $rules_link->name;
  88. }
  89. /**
  90. * Returns the name condition set to a rules link.
  91. */
  92. function rules_link_get_rules_set_name($rules_link) {
  93. return 'rules_link_set_' . $rules_link->name;
  94. }
  95. /**
  96. * Loads and returns the condition to a link. If it
  97. * doesn't exist, a new one will be created.
  98. *
  99. * @param $rules_link
  100. * The rules link to which the condition should be loaded.
  101. */
  102. function rules_link_load_condition_set($rules_link) {
  103. $condition_set = rules_config_load(rules_link_get_condition_set_name($rules_link));
  104. if ($condition_set != FALSE) {
  105. return $condition_set;
  106. }
  107. else {
  108. $conditions = rules_and(array(
  109. $rules_link->entity_type => array('type' => $rules_link->entity_type, 'label' => $rules_link->entity_type),
  110. ));
  111. $conditions->label = 'Rules link: ' . $rules_link->label . ' condition';
  112. $conditions->save(rules_link_get_condition_set_name($rules_link), 'rules_link');
  113. return $conditions;
  114. }
  115. }
  116. /**
  117. * Loads and returns the rules set to a link. If it
  118. * doesn't exist, a new one will be created.
  119. *
  120. * @param $rules_link
  121. * The rules link to which the condition or rules set should be loaded.
  122. */
  123. function rules_link_load_rules_set($rules_link) {
  124. $rule_set = rules_config_load(rules_link_get_rules_set_name($rules_link));
  125. if ($rule_set != FALSE) {
  126. return $rule_set;
  127. }
  128. else {
  129. $rule_set = rules_rule_set(array(
  130. $rules_link->entity_type => array('type' => $rules_link->entity_type, 'label' => $rules_link->entity_type),
  131. ));
  132. $rule_set->label = 'Rules link: ' . $rules_link->label . ' rules set';
  133. $rule_set->save(rules_link_get_rules_set_name($rules_link), 'rules_link');
  134. return $rule_set;
  135. }
  136. }
  137. /**
  138. * Renders a link using the name of the rules_link and the entity id.
  139. *
  140. * @param $rules_link_name
  141. * The name the link which should be rendered.
  142. * @param $entity_id
  143. * The entity id of entity on which the rule should be triggered.
  144. * @param $destination
  145. * The destination to which the Rules Module should redirect the user after
  146. * triggering the link.
  147. * @param $parameters
  148. * Additional parameters for the Rules components of the link.
  149. *
  150. * @return
  151. * A renderable array.
  152. */
  153. function rules_link_render($rules_link_name, $entity_id, $destination = NULL, $parameters = array()) {
  154. $rules_link = rules_link_load($rules_link_name);
  155. return rules_link_render_link($rules_link, $entity_id, $destination, $parameters);
  156. }
  157. /**
  158. * Renders a link.
  159. *
  160. * @param $rules_link
  161. * The link which should be rendered.
  162. * @param $entity_id
  163. * The entity id of entity on which the rule should be triggered.
  164. * @param $destination
  165. * The destination to which the Rules Module should redirect the user after
  166. * triggering the link.
  167. * @param $parameters
  168. * Additional parameters for the Rules components of the link.
  169. *
  170. * @return
  171. * A renderable array.
  172. */
  173. function rules_link_render_link($rules_link, $entity_id, $destination = NULL, $parameters = array()) {
  174. if (rules_link_check_visibility($rules_link, array_merge(array($entity_id), $parameters))) {
  175. $path = $rules_link->path . '/' . $entity_id;
  176. if (count($parameters) > 0) {
  177. $path .= '/' . implode('/', $parameters);
  178. }
  179. $path .= $rules_link->settings['link_type'] == 'confirm' ? '' : '/' . rules_link_get_token($entity_id);
  180. $link = array(
  181. '#title' => $rules_link->getSettingTranslation('text'),
  182. '#href' => $path,
  183. '#attr' => array('class' => array('rules-link'), 'rel' => 'nofollow'),
  184. '#rules_link' => $rules_link,
  185. '#theme' => 'rules_link',
  186. );
  187. if ($rules_link->settings['link_type'] == 'javascript') {
  188. $link['#attr']['class'][] = 'rules-link-js';
  189. drupal_add_js(drupal_get_path('module', 'rules_link') . '/rules_link.js', 'file');
  190. drupal_add_css(drupal_get_path('module', 'rules_link') . '/rules_link.css', 'file');
  191. }
  192. else {
  193. $link['#options'] = array('query' => $destination);
  194. }
  195. return $link;
  196. }
  197. return array();
  198. }
  199. /**
  200. * Trims a whitespaces from a parameter.
  201. */
  202. function rules_link_trim_parameters(&$value) {
  203. $value = trim($value);
  204. }
  205. /**
  206. * Custom Entity class.
  207. */
  208. class RulesLink extends Entity {
  209. public $settings = array();
  210. public function __construct($values = array()) {
  211. parent::__construct($values, 'rules_link');
  212. }
  213. /**
  214. * Gets the i18n translation of a setting.
  215. *
  216. * @param $name
  217. * The setting name.
  218. * @param $langcode
  219. * The optional language code. Defaults to the current display language.
  220. *
  221. * @see Entity::getTranslation()
  222. */
  223. public function getSettingTranslation($name, $langcode = NULL) {
  224. $value = isset($this->settings[$name]) ? $this->settings[$name] : NULL;
  225. $i18n_name = 'rules_link:rules_link:' . $this->identifier() . ':' . $name;
  226. return entity_i18n_string($i18n_name, $value, $langcode);
  227. }
  228. }
  229. /**
  230. * Implements hook_rules_link_insert().
  231. */
  232. function rules_link_rules_link_insert($link) {
  233. // Do not directly issue menu rebuilds here to avoid potentially multiple
  234. // rebuilds. Instead, let menu_get_item() issue the rebuild on the next page.
  235. variable_set('menu_rebuild_needed', TRUE);
  236. }
  237. /**
  238. * Implements hook_rules_link_update().
  239. */
  240. function rules_link_rules_link_update($link) {
  241. // Make sure to only issue menu rebuilds if necessary.
  242. // @see rules_link_rules_link_insert()
  243. if (!empty($link->original) && ($link->path != $link->original->path || $link->settings['link_type'] != $link->original->settings['link_type'])) {
  244. variable_set('menu_rebuild_needed', TRUE);
  245. }
  246. }
  247. /**
  248. * Implements hook_rules_link_delete().
  249. */
  250. function rules_link_rules_link_delete($link) {
  251. // @see rules_link_rules_link_insert()
  252. variable_set('menu_rebuild_needed', TRUE);
  253. // Delete associate rule configs.
  254. rules_link_load_condition_set($link)->delete();
  255. rules_link_load_rules_set($link)->delete();
  256. }
  257. /**
  258. * Generates a token used to protect links from spoofing.
  259. */
  260. function rules_link_get_token($content_id) {
  261. // Anonymous users get a less secure token, since it must be the same for all
  262. // anonymous users on the entire site to work with page caching.
  263. return ($GLOBALS['user']->uid) ? drupal_get_token($content_id) : drupal_hmac_base64($content_id, drupal_get_private_key() . drupal_get_hash_salt());
  264. }
  265. /**
  266. * Checks if the given token is correct.
  267. */
  268. function rules_link_check_token($token, $content_id) {
  269. return rules_link_get_token($content_id) === $token;
  270. }
  271. function rules_link_get_paramters($rules_link) {
  272. $args = arg();
  273. // Remove the first arguments, that represent the url of the link.
  274. $path_args = explode('/', $rules_link->path);
  275. $args = array_slice($args, count($path_args));
  276. return $args;
  277. }
  278. function rules_link_invoke_component($name, $args) {
  279. if ($component = rules_get_cache('comp_' . $name)) {
  280. return $component->executeByArgs($args);
  281. }
  282. }
  283. function rules_link_check_visibility($rules_link, $args) {
  284. $rule_set = rules_link_load_rules_set($rules_link);
  285. $paramInfo = $rule_set->parameterInfo();
  286. return count($args) >= count($paramInfo) && user_access("access rules link " . $rules_link->name) && rules_link_invoke_component(rules_link_get_condition_set_name($rules_link), $args);
  287. }
  288. /**
  289. * Triggers a rule set from a rules link.
  290. */
  291. function rules_link_trigger($rules_link, $entity_id) {
  292. rules_link_invoke_component(rules_link_get_rules_set_name($rules_link), rules_link_get_paramters($rules_link));
  293. }
  294. /**
  295. * Menu callback for javascript links.
  296. */
  297. function rules_link_trigger_js($rules_link, $entity_id) {
  298. rules_link_trigger($rules_link, $entity_id);
  299. $json = array(
  300. 'message' => drupal_get_messages(),
  301. );
  302. drupal_json_output($json);
  303. }
  304. /**
  305. * Menu callback for token links.
  306. */
  307. function rules_link_trigger_token($rules_link, $entity_id) {
  308. rules_link_trigger($rules_link, $entity_id);
  309. drupal_goto();
  310. }
  311. /**
  312. * Form generator for the menu callback for the confirm form links.
  313. */
  314. function rules_link_trigger_form($form, &$form_state, $rules_link, $entity_id) {
  315. $form['link'] = array(
  316. '#type' => 'hidden',
  317. '#value' => $rules_link->name,
  318. );
  319. $form['entity_id'] = array(
  320. '#type' => 'hidden',
  321. '#value' => $entity_id,
  322. );
  323. return confirm_form($form, filter_xss_admin($rules_link->getSettingTranslation('confirm_question')), '', filter_xss_admin($rules_link->getSettingTranslation('confirm_description')));
  324. }
  325. /**
  326. * Submit function for the confirm form links.
  327. */
  328. function rules_link_trigger_form_submit($form, &$form_state) {
  329. $rules_link = rules_link_load($form_state['values']['link']);
  330. rules_link_trigger($rules_link, $form_state['values']['entity_id']);
  331. }
  332. /**
  333. * Access callback function for confirm type links.
  334. */
  335. function rules_link_access_link_confirm($rules_link, $entity_id) {
  336. if (rules_link_check_visibility($rules_link, rules_link_get_paramters($rules_link))) {
  337. return TRUE;
  338. }
  339. return FALSE;
  340. }
  341. /**
  342. * Access callback function for the token and javascript links.
  343. */
  344. function rules_link_access_link($rules_link, $entity_id) {
  345. // The token is always the last element of the argument array.
  346. $params = rules_link_get_paramters($rules_link);
  347. $token = array_pop($params);
  348. return rules_link_check_token($token, $entity_id) && rules_link_access_link_confirm($rules_link, $entity_id);
  349. }
  350. /**
  351. * Implements hook_menu().
  352. */
  353. function rules_link_menu() {
  354. $item = array();
  355. foreach (rules_link_get_links() as $name => $link) {
  356. $first_arg = (count(explode('/', $link->path)));
  357. switch ($link->settings['link_type']) {
  358. case 'javascript':
  359. $item[$link->path . '/%/%'] = array(
  360. 'page callback' => 'rules_link_trigger_js',
  361. 'page arguments' => array($link, $first_arg),
  362. 'access arguments' => array($link, $first_arg, $first_arg + 1),
  363. 'access callback' => 'rules_link_access_link',
  364. 'type' => MENU_CALLBACK,
  365. );
  366. break;
  367. case 'token':
  368. $item[$link->path . '/%/%'] = array(
  369. 'page callback' => 'rules_link_trigger_token',
  370. 'page arguments' => array($link, $first_arg),
  371. 'access arguments' => array($link, $first_arg, $first_arg + 1),
  372. 'access callback' => 'rules_link_access_link',
  373. 'type' => MENU_CALLBACK,
  374. );
  375. break;
  376. case 'confirm':
  377. $item[$link->path . '/%'] = array(
  378. 'page callback' => 'drupal_get_form',
  379. 'page arguments' => array('rules_link_trigger_form', $link, $first_arg),
  380. 'access arguments' => array($link, $first_arg),
  381. 'access callback' => 'rules_link_access_link_confirm',
  382. 'type' => MENU_CALLBACK,
  383. );
  384. break;
  385. }
  386. }
  387. return $item;
  388. }
  389. /**
  390. * Implements hook_theme().
  391. */
  392. function rules_link_theme() {
  393. return array(
  394. 'rules_link' => array(
  395. 'template' => 'rules-link',
  396. 'variables' => array('title' => NULL, 'href' => NULL, 'attr' => NULL, 'options' => NULL, 'rules_link' => NULL),
  397. 'pattern' => 'rules-link__',
  398. ),
  399. );
  400. }
  401. /**
  402. * Implements hook_theme_registry_alter().
  403. */
  404. function rules_link_theme_registry_alter(&$theme_registry) {
  405. // Override the base hook so that we do not overlay with the default entity
  406. // theme hook defined in entity_theme().
  407. // Directly changing this setting in rules_link_theme() did not work.
  408. $theme_registry['rules_link']['base hook'] = 'rules_link';
  409. }
  410. /**
  411. * Implements hook_views_api().
  412. */
  413. function rules_link_views_api() {
  414. return array(
  415. 'api' => '3.0',
  416. );
  417. }
  418. function template_preprocess_rules_link(&$variables) {
  419. if (!isset($variables['options'])) {
  420. $variables['options'] = array();
  421. }
  422. $variables['options'] += array('html' => FALSE);
  423. $variables['title'] = ($variables['options']['html'] ? $variables['title'] : check_plain($variables['title']));
  424. if (isset($options['attr']['title']) && strpos($options['attr']['title'], '<') !== FALSE) {
  425. $variables['attr']['title'] = strip_tags($options['attr']['title']);
  426. } else if (!isset($options['attr']['title'])) {
  427. $variables['attr']['title'] = $variables['title'];
  428. }
  429. $variables['href'] = check_plain(url($variables['href'], $variables['options']));
  430. $variables['attr'] = drupal_attributes($variables['attr']);
  431. }
  432. /**
  433. * Implement hook_entity_view().
  434. */
  435. function rules_link_entity_view($entity, $type, $view_mode, $langcode) {
  436. $links = array();
  437. $rules_links = rules_link_get_links();
  438. foreach ($rules_links as $name => $rules_link) {
  439. if ($rules_link->entity_type == $type && $rules_link->settings['entity_link']) {
  440. list($id, $rev, $bundle) = entity_extract_ids($type, $entity);
  441. // If the link is restricted to some bundles, verify the bundle.
  442. if ($id && (empty($rules_link->settings['bundles']) || in_array($bundle, $rules_link->settings['bundles']))) {
  443. $renderd_link = rules_link_render_link($rules_link, $id, drupal_get_destination());
  444. if (!empty($renderd_link)) {
  445. $links[$name] = drupal_render($renderd_link);
  446. }
  447. }
  448. }
  449. }
  450. foreach ($links as $name => $link) {
  451. $entity->content['rules_links_' . $name] = array(
  452. '#markup' => $link,
  453. );
  454. }
  455. }
  456. /**
  457. * Implements hook_features_pipe_component_alter() for fields.
  458. */
  459. function rules_link_features_pipe_rules_link_alter(&$pipe, $data, $export) {
  460. foreach ($data as $id) {
  461. $rules_link = entity_load_single('rules_link', $id);
  462. $pipe['rules_config'][] = rules_link_get_condition_set_name($rules_link);
  463. $pipe['rules_config'][] = rules_link_get_rules_set_name($rules_link);
  464. }
  465. }
  466. /**
  467. * Alter the breadcrumb trail of the rules components.
  468. */
  469. function rules_link_menu_breadcrumb_alter(&$active_trail, $item) {
  470. if (substr($item['href'], 22, 34) == 'rules/components/manage/rules_link') {
  471. // Parse the name out of the link.
  472. if (substr($item['href'], 57, 3) == 'set') {
  473. $start = 61;
  474. }
  475. elseif (substr($item['href'], 57, 3) == 'con') {
  476. $start = 67;
  477. }
  478. $link_name = substr($item['href'], $start, strlen($item['href']));
  479. $pos = strpos($link_name, '/') ;
  480. if ($pos !== FALSE) {
  481. $link_name = substr($link_name, 0, $pos);
  482. }
  483. $rules_link = rules_link_load($link_name);
  484. if ($rules_link) {
  485. // Replace the link to Rules with a link to Rules Link.
  486. $active_trail[4]['title'] = 'Rules Links';
  487. $active_trail[4]['href'] = 'admin/config/workflow/rules_links';
  488. $active_trail[4]['options']['attributes']['title'] = 'Manage links that triggers rules.';
  489. $active_trail[4]['localized_options'] = array();
  490. // Replace component link with link to the current rules link.
  491. $active_trail[5]['title'] = $rules_link->label;
  492. $active_trail[5]['href'] = "admin/config/workflow/rules_links/manage/$link_name/components";
  493. $active_trail[5]['options']['attributes']['title'] = 'Edit the current link.';
  494. $active_trail[5]['localized_options'] = array();
  495. }
  496. }
  497. }
  498. /**
  499. * Implements hook_field_extra_fields().
  500. */
  501. function rules_link_field_extra_fields() {
  502. $return = array();
  503. $rules_links = rules_link_get_links();
  504. foreach ($rules_links as $rules_link) {
  505. if ($rules_link->settings['entity_link']) {
  506. $entity_info = entity_get_info($rules_link->entity_type);
  507. $bundles = empty($rules_link->settings['bundles']) ? array_keys($entity_info['bundles']) : $rules_link->settings['bundles'];
  508. foreach ($bundles as $bundle) {
  509. $return[$rules_link->entity_type][$bundle]['display']['rules_links_' . $rules_link->name]['label'] = $rules_link->label;
  510. $return[$rules_link->entity_type][$bundle]['display']['rules_links_' . $rules_link->name]['description'] = '';
  511. $return[$rules_link->entity_type][$bundle]['display']['rules_links_' . $rules_link->name]['weight'] = 0;
  512. }
  513. }
  514. }
  515. return $return;
  516. }