tac_lite.module 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. <?php
  2. /**
  3. * @file
  4. * Control access to site content based on taxonomy, roles and users.
  5. *
  6. *
  7. */
  8. /**
  9. * Implementation of hook_help().
  10. */
  11. function tac_lite_help($section) {
  12. switch ($section) {
  13. case 'admin/help#tac_lite':
  14. // $output .= '<p>' . t('') . '</p>';
  15. $output = '<p>' . t('Taxonomy Access Control Lite allows you to restrict access to site content. It uses a simple scheme based on Taxonomy, Users and Roles.') . '</p>';
  16. $output .= '<p>' . t('This module leverages Drupal\'s node_access table allows this module to grant permission to view, update, and delete nodes. To control which users can <em>create</em> new nodes, use Drupal\'s role based permissions.') . '</p>';
  17. $output .= '<p>' . t('It is important to understand that this module <em>grants</em> privileges, as opposed to <em>revoking</em> privileges. So, use Drupal\'s built-in permissions to hide content from certain roles, then use this module to show the content. This module cannot hide content that the user is allowed to see based on their existing privileges.') . '</p>';
  18. $output .= '<p>' . t('There are several steps required to set up Taxonomy Access Control Lite.') . '</p>';
  19. $output .= '<ol>';
  20. $output .= '<li>' . t('Define one or more vocabularies whose terms will control which users have access. For example, you could define a vocabulary called \'Privacy\' with terms \'Public\' and \'Private\'.') . '</li>';
  21. $output .= '<li>' . t('Tell this module which vocabularies control privacy. (!link)', array('!link' => l(t('administer -> people -> access control by taxonomy'), 'admin/people/access/tac_lite'))) . '</li>';
  22. $output .= '<li>' . t('Configure one or more <em>schemes</em>. simple site may need only one scheme which grants view permission. A more complex site might require additional schemes for update and delete. Each scheme associates roles and terms. Users will be granted priviliges based on their role and the terms with which nodes are tagged.') . '</li>';
  23. $output .= '<li>' . t('When settings are correct, <a href=!url>rebuild node_access permissions</a>.', array(
  24. '!url' => url('admin/reports/status/rebuild'),
  25. )) . '</li>';
  26. $output .= '<li>' . t('Optionally, grant access to individual users. (See the <em>access by taxonomy</em> tab, under user -> edit.)') . '</li>';
  27. $output .= '</ol>';
  28. $output .= '<p>' . t('Troubleshooting:.') . '<ul>';
  29. $output .= '<li>' . t('Try rebuilding node_access permissions.') . '</li>';
  30. $output .= '<li>' . t('Try disabling tac_lite.module, rebuilding permissions. With the module disabled, users should not have the privileges you are attempting to grant with this module.') . '</li>';
  31. $output .= '<li>' . t('The devel_node_access.module (part of <a href=!url>devel</a>) helps to see exactly what Drupal\'s node_access table is doing.', array(
  32. '!url' => 'http://drupal.org/project/devel',
  33. )) . '</li>';
  34. $output .= '</ul></p>';
  35. return $output;
  36. break;
  37. }
  38. }
  39. /**
  40. * Implementation of hook_perm().
  41. */
  42. function tac_lite_permission() {
  43. return array(
  44. 'administer tac_lite' => array(
  45. 'title' => t('administer tac_lite'),
  46. 'description' => t('TODO Add a description for \'administer tac_lite\''),
  47. ),
  48. );
  49. }
  50. /**
  51. * Implementation of hook_menu().
  52. */
  53. function tac_lite_menu() {
  54. global $user;
  55. $items = array();
  56. $items['admin/config/people/tac_lite'] = array(
  57. 'title' => 'Access by Taxonomy',
  58. 'description' => "taxonomy-based permissions by tac_lite",
  59. 'page callback' => 'drupal_get_form',
  60. 'page arguments' => array('tac_lite_admin_settings'),
  61. 'weight' => 1, // after 'roles' tab
  62. 'access arguments' => array('administer tac_lite'),
  63. );
  64. $items['admin/config/people/tac_lite/settings'] = array(
  65. 'title' => 'Settings',
  66. 'type' => MENU_DEFAULT_LOCAL_TASK,
  67. 'weight' => -1,
  68. 'access arguments' => array('administer tac_lite'),
  69. );
  70. $schemes = variable_get('tac_lite_schemes', 1);
  71. for ($i = 1; $i <= $schemes; $i++) {
  72. $scheme = variable_get('tac_lite_config_scheme_'. $i, FALSE);
  73. if ($scheme) {
  74. $title = $scheme['name'];
  75. } else {
  76. $title = "Scheme $i";
  77. }
  78. $items['admin/config/people/tac_lite/scheme_' . $i] = array(
  79. 'title' => $title,
  80. 'page callback' => 'tac_lite_admin_settings_scheme',
  81. 'page arguments' => array((string)$i),
  82. 'type' => MENU_LOCAL_TASK,
  83. 'weight' => $i,
  84. 'access arguments' => array('administer tac_lite'),
  85. );
  86. }
  87. return $items;
  88. }
  89. /**
  90. * Returns the settings form
  91. */
  92. function tac_lite_admin_settings($form, &$form_state) {
  93. $vocabularies = taxonomy_get_vocabularies();
  94. if (!count($vocabularies)) {
  95. $form['body'] = array(
  96. '#type' => 'markup',
  97. '#markup' => t('You must <a href="!url">create a vocabulary</a> before you can use tac_lite.',
  98. array('!url' => url('admin/structure/taxonomy/add/vocabulary'))),
  99. );
  100. return $form;
  101. }
  102. else {
  103. $options = array();
  104. foreach ($vocabularies as $vid => $vocab) {
  105. $options[$vid] = $vocab->name;
  106. }
  107. $form['tac_lite_categories'] = array(
  108. '#type' => 'select',
  109. '#title' => t('Vocabularies'),
  110. '#default_value' => variable_get('tac_lite_categories', NULL),
  111. '#options' => $options,
  112. '#description' => t('Select one or more vocabularies to control privacy. <br/>Use caution with hierarchical (nested) taxonomies as <em>visibility</em> settings may cause problems on node edit forms.<br/>Do not select free tagging vocabularies, they are not supported.'),
  113. '#multiple' => TRUE,
  114. '#required' => TRUE,
  115. );
  116. $scheme_options = array();
  117. // Currently only view, edit, delete permissions possible, so 7
  118. // permutations will be more than enough.
  119. for ($i = 1; $i < 8; $i++)
  120. $scheme_options[$i] = $i;
  121. $form['tac_lite_schemes'] = array(
  122. '#type' => 'select',
  123. '#title' => t('Number of Schemes'),
  124. '#description' => t('Each scheme allows for a different set of permissions. For example, use scheme 1 for read-only permission; scheme 2 for read and update; scheme 3 for delete; etc. Additional schemes increase the size of your node_access table, so use no more than you need.'),
  125. '#default_value' => variable_get('tac_lite_schemes', 1),
  126. '#options' => $scheme_options,
  127. '#required' => TRUE,
  128. );
  129. $form['tac_lite_rebuild'] = array(
  130. '#type' => 'checkbox',
  131. '#title' => t('Rebuild content permissions now'),
  132. '#default_value' => FALSE, // default false because usually only needed after scheme has been changed.
  133. '#description' => t('Do this once, after you have fully configured access by taxonomy.'),
  134. '#weight' => 9,
  135. );
  136. $ret = system_settings_form($form);
  137. // Special handling is required when this form is submitted.
  138. $ret['#submit'][] = '_tac_lite_admin_settings_submit';
  139. return $ret;
  140. }
  141. }
  142. /**
  143. * This form submit callback ensures that the form values are saved, and also
  144. * the node access database table is rebuilt.
  145. * 2008 : Modified by Paulo to be compliant with drupal 6
  146. */
  147. function _tac_lite_admin_settings_submit($form, &$form_state) {
  148. $rebuild = $form_state['values']['tac_lite_rebuild'];
  149. // Rebuild the node_access table.
  150. if ($rebuild) {
  151. node_access_rebuild(TRUE);
  152. }
  153. else {
  154. drupal_set_message(t('Do not forget to <a href=!url>rebuild node access permissions</a> after you have configured taxonomy-based access.', array(
  155. '!url' => url('admin/reports/status/rebuild'),
  156. )), 'warning');
  157. }
  158. // And rebuild menus, in case the number of schemes has changed.
  159. menu_rebuild();
  160. variable_del('tac_lite_rebuild'); // We don't need to store this as a system variable.
  161. }
  162. /**
  163. * Menu callback to create a form for each scheme.
  164. * @param $i
  165. * The index of the scheme that we will be creating a form for. Passed in as a page argument from the menu.
  166. */
  167. function tac_lite_admin_settings_scheme($i) {
  168. return drupal_get_form('tac_lite_admin_scheme_form', $i);
  169. }
  170. /**
  171. * helper function
  172. */
  173. function _tac_lite_config($scheme) {
  174. // different defaults for scheme 1
  175. if ($scheme === 1) {
  176. $config = variable_get('tac_lite_config_scheme_' . $scheme, array(
  177. 'name' => t('read'),
  178. 'perms' => array('grant_view'),
  179. ));
  180. }
  181. else {
  182. $config = variable_get('tac_lite_config_scheme_' . $scheme, array(
  183. 'name' => NULL,
  184. 'perms' => array(),
  185. ));
  186. }
  187. // Merge defaults, for backward compatibility.
  188. $config += array(
  189. 'term_visibility' => (isset($config['perms']['grant_view']) && $config['perms']['grant_view']),
  190. 'unpublished' => FALSE,
  191. );
  192. // For backward compatability, use naming convention for scheme 1
  193. if ($scheme == 1) {
  194. $config['realm'] = 'tac_lite';
  195. }
  196. else {
  197. $config['realm'] = 'tac_lite_scheme_' . $scheme;
  198. }
  199. return $config;
  200. }
  201. /**
  202. * Returns the form for role-based privileges.
  203. */
  204. function tac_lite_admin_scheme_form($form, $form_state, $i) {
  205. $vids = variable_get('tac_lite_categories', NULL);
  206. $roles = user_roles();
  207. $config = _tac_lite_config($i);
  208. $form['#tac_lite_config'] = $config;
  209. if (count($vids)) {
  210. $form['tac_lite_config_scheme_' . $i] = array('#tree' => TRUE);
  211. $form['tac_lite_config_scheme_' . $i]['name'] = array(
  212. '#type' => 'textfield',
  213. '#title' => t('Scheme name'),
  214. '#description' => t('A human-readable name for administrators to see. For example, \'read\' or \'read and write\'.'),
  215. '#default_value' => $config['name'],
  216. '#required' => TRUE,
  217. );
  218. // Currently, only view, update and delete are supported by node_access
  219. $options = array(
  220. 'grant_view' => 'view',
  221. 'grant_update' => 'update',
  222. 'grant_delete' => 'delete',
  223. );
  224. $form['tac_lite_config_scheme_' . $i]['perms'] = array(
  225. '#type' => 'select',
  226. '#title' => t('Permissions'),
  227. '#multiple' => TRUE,
  228. '#options' => $options,
  229. '#default_value' => $config['perms'],
  230. '#description' => t('Select which permissions are granted by this scheme. <br/>Note when granting update, it is best to enable visibility on all terms. Otherwise a user may unknowingly remove invisible terms while editing a node.'),
  231. '#required' => FALSE,
  232. );
  233. $form['tac_lite_config_scheme_' . $i]['unpublished'] = array(
  234. '#type' => 'checkbox',
  235. '#title' => t('Apply to unpublished content'),
  236. '#description' => t('If checked, permissions in this scheme will apply to unpublished content. If this scheme includes the view permission, then <strong>unpublished nodes will be visible</strong> to users whose roles would grant them access to the published node.'),
  237. '#default_value' => $config['unpublished'],
  238. );
  239. $form['tac_lite_config_scheme_' . $i]['term_visibility'] = array(
  240. '#type' => 'checkbox',
  241. '#title' => t('Visibility'),
  242. '#description' => t('If checked, this scheme determines whether a user can view <strong>terms</strong>. Note the <em>view</em> permission in the select field above refers to <strong>node</strong> visibility. This checkbox refers to <strong>term</strong> visibility, for example in a content edit form or tag cloud.'),
  243. '#default_value' => $config['term_visibility'],
  244. );
  245. $form['helptext'] = array(
  246. '#type' => 'markup',
  247. '#markup' => t('To grant to an individual user, visit the <em>access by taxonomy</em> tab on the account edit page.'),
  248. '#prefix' => '<p>',
  249. '#suffix' => '</p>',
  250. );
  251. $form['helptext2'] = array(
  252. '#type' => 'markup',
  253. '#markup' => t('To grant by role, select the terms below.'),
  254. '#prefix' => '<p>',
  255. '#suffix' => '</p>',
  256. );
  257. $vocabularies = taxonomy_get_vocabularies();
  258. $all_defaults = variable_get('tac_lite_grants_scheme_' . $i, array());
  259. $form['tac_lite_grants_scheme_' . $i] = array('#tree' => TRUE);
  260. foreach ($roles as $rid => $role_name) {
  261. $form['tac_lite_grants_scheme_' . $i][$rid] = array(
  262. '#type' => 'fieldset',
  263. '#tree' => TRUE,
  264. '#title' => check_plain(t('Grant permission by role: !role', array('!role' => $role_name))),
  265. '#description' => t(''),
  266. '#collapsible' => TRUE,
  267. );
  268. $defaults = isset($all_defaults[$rid]) ? $all_defaults[$rid] : NULL;
  269. foreach ($vids as $vid) {
  270. // Build a taxonomy select form element for this vocab
  271. $v = $vocabularies[$vid];
  272. $tree = taxonomy_get_tree($v->vid);
  273. $options = array(0 => '<' . t('none') . '>');
  274. if ($tree) {
  275. foreach ($tree as $term) {
  276. $choice = new stdClass();
  277. $choice->option = array($term->tid => str_repeat('-', $term->depth) . $term->name);
  278. $options[] = $choice;
  279. }
  280. }
  281. $default_values = isset($defaults[$vid]) ? $defaults[$vid] : NULL;
  282. $form['tac_lite_grants_scheme_' . $i][$rid][$vid] = _tac_lite_term_select($v, $default_values);
  283. }
  284. }
  285. $form['tac_lite_rebuild'] = array(
  286. '#type' => 'checkbox',
  287. '#title' => t('Rebuild content permissions now'),
  288. '#default_value' => FALSE, // default false because usually only needed after scheme has been changed.
  289. '#description' => t('Do this once, after you have fully configured access by taxonomy.'),
  290. '#weight' => 9,
  291. );
  292. $form['#submit'][] = 'tac_lite_admin_scheme_form_submit';
  293. return system_settings_form($form);
  294. }
  295. else {
  296. $form['tac_lite_help'] = array(
  297. '#type' => 'markup',
  298. '#prefix' => '<p>', '#suffix' => '</p>',
  299. '#markup' => t('First, select one or more vocabularies on the <a href=!url>settings tab</a>. Then, return to this page to complete configuration.', array('!url' => url('admin/config/people/tac_lite/settings'))));
  300. return $form;
  301. }
  302. }
  303. /**
  304. * Submit function for admin settings form to rebuild the menu.
  305. */
  306. function tac_lite_admin_scheme_form_submit($form, &$form_state) {
  307. variable_set('menu_rebuild_needed', TRUE);
  308. // Rebuild the node_access table.
  309. if ($form_state['values']['tac_lite_rebuild']) {
  310. node_access_rebuild(TRUE);
  311. }
  312. else {
  313. drupal_set_message(t('Do not forget to <a href=!url>rebuild node access permissions</a> after you have configured taxonomy-based access.', array(
  314. '!url' => url('admin/reports/status/rebuild'),
  315. )), 'warning');
  316. }
  317. variable_del('tac_lite_rebuild'); // We don't need to store this as a system variable.
  318. }
  319. /**
  320. * Implementation of hook_user_categories
  321. *
  322. * Creates the user edit category form for tac_lite's user-specific permissions under user/edit
  323. */
  324. function tac_lite_user_categories() {
  325. return array(
  326. array(
  327. 'name' => 'tac_lite',
  328. 'title' => t('Access by taxonomy'),
  329. 'weight' => 5,
  330. 'access callback' => 'user_access',
  331. 'access arguments' => array('administer users'),
  332. ),
  333. );
  334. }
  335. /**
  336. * Implementation of hook_form_alter().
  337. *
  338. * @param $form
  339. * Nested array of form elements that comprise the form.
  340. * @param $form_state
  341. * A keyed array containing the current state of the form. The arguments that drupal_get_form() was originally called with are available in the array $form_state['build_info']['args'].
  342. * @param $form_id
  343. * String representing the name of the form itself. Typically this is the name of the function that generated the form.
  344. *
  345. */
  346. function tac_lite_form_alter(&$form, &$form_state, $form_id){
  347. // Catch for the tac_lite category on the user edit form.
  348. if ($form_id == 'user_profile_form') {
  349. if ($form['#user_category'] == 'tac_lite') {
  350. $vocabularies = taxonomy_get_vocabularies();
  351. $vids = variable_get('tac_lite_categories', NULL);
  352. if (count($vids)) {
  353. for ($i = 1; $i <= variable_get('tac_lite_schemes', 1); $i++) {
  354. $config = _tac_lite_config($i);
  355. if ($config['name']) {
  356. $perms = $config['perms'];
  357. if ($config['term_visibility']) {
  358. $perms[] = t('term visibility');
  359. }
  360. $form['tac_lite'][$config['realm']] = array(
  361. '#type' => 'fieldset',
  362. '#title' => $config['name'],
  363. '#description' => t('This scheme controls %perms.', array('%perms' => implode(' and ', $perms))),
  364. '#tree' => TRUE,
  365. );
  366. // Create a form element for each vocabulary
  367. foreach ($vids as $vid) {
  368. $v = $vocabularies[$vid];
  369. // TODO: Should we be looking in form_state also for the default value?
  370. // (Might only be necessary if we are adding in custom validation?)
  371. $default_values = array();
  372. if (!empty($form['#user']->data[$config['realm']])) {
  373. if (isset($form['#user']->data[$config['realm']][$vid])) {
  374. $default_values = $form['#user']->data[$config['realm']][$vid];
  375. }
  376. }
  377. $form['tac_lite'][$config['realm']][$vid] = _tac_lite_term_select($v, $default_values);
  378. $form['tac_lite'][$config['realm']][$vid]['#description'] =
  379. t('Grant permission to this user by selecting terms. Note that permissions are in addition to those granted based on user roles.');
  380. }
  381. }
  382. }
  383. $form['tac_lite'][0] = array(
  384. '#type' => 'markup',
  385. '#markup' => '<p>' . t('You may grant this user access based on the schemes and terms below. These permissions are in addition to <a href="!url">role based grants on scheme settings pages</a>.',
  386. array('!url' => url('admin/config/people/tac_lite/scheme_1'))) . "</p>\n",
  387. '#weight' => -1,
  388. );
  389. }
  390. else {
  391. // TODO: Do we need to handle the situation where no vocabularies have been set up yet / none have been assigned to tac_lite?
  392. }
  393. return $form;
  394. }
  395. }
  396. }
  397. /**
  398. * Implementation of hook_user_presave().
  399. *
  400. * Move the tac_lite data into the data object
  401. * @param $edit
  402. * The array of form values submitted by the user.
  403. * @param $account
  404. * The user object on which the operation is performed.
  405. * @param $category
  406. * The active category of user information being edited.
  407. */
  408. function tac_lite_user_presave(&$edit, $account, $category) {
  409. // Only proceed if we are in the tac_lite category.
  410. if ($category == 'tac_lite'){
  411. // Go through each scheme and copy the form value into the data element
  412. for ($i = 1; $i <= variable_get('tac_lite_schemes', 1); $i++) {
  413. $config = _tac_lite_config($i);
  414. if ($config['name']) {
  415. $edit['data'][$config['realm']] = $edit[$config['realm']];
  416. }
  417. }
  418. }
  419. }
  420. /**
  421. * Implements hook_node_access_records().
  422. *
  423. * We are given a node and we return records for the node_access table. In
  424. * our case, we inpect the node's taxonomy and grant permissions based on the
  425. * terms.
  426. */
  427. function tac_lite_node_access_records($node) {
  428. // Get the tids we care about that are assigned to this node
  429. $tids = _tac_lite_get_terms($node);
  430. if (!count($tids)) {
  431. // no relevant terms found.
  432. // in drupal 4-7 we had to write a row into the database. In drupal 5 and later, it should be safe to do nothing.
  433. }
  434. else {
  435. // if we're here, the node has terms associated with it which restrict
  436. // access to the node.
  437. $grants = array();
  438. for ($i = 1; $i <= variable_get('tac_lite_schemes', 1); $i++) {
  439. $config = _tac_lite_config($i);
  440. // Only apply grants to published nodes, or unpublished nodes if requested in the scheme
  441. if ($node->status || $config['unpublished']) {
  442. foreach ($tids as $tid) {
  443. $grant = array(
  444. 'realm' => $config['realm'],
  445. 'gid' => $tid, // use term id as grant id
  446. 'grant_view' => 0,
  447. 'grant_update' => 0,
  448. 'grant_delete' => 0,
  449. 'priority' => 0,
  450. );
  451. foreach ($config['perms'] as $perm) {
  452. $grant[$perm] = TRUE;
  453. }
  454. $grants[] = $grant;
  455. }
  456. }
  457. }
  458. return $grants;
  459. }
  460. }
  461. /**
  462. * Gets terms from a node that belong to vocabularies selected for use by tac_lite
  463. *
  464. * @param $node
  465. * A node object
  466. * @return
  467. * An array of term ids
  468. */
  469. function _tac_lite_get_terms($node) {
  470. $tids = array();
  471. // Get the vids that tac_lite cares about.
  472. $vids = variable_get('tac_lite_categories', NULL);
  473. if ($vids) {
  474. // Load all terms found in term reference fields.
  475. // This logic should work for all nodes (published or not).
  476. $terms_by_vid = tac_lite_node_get_terms($node);
  477. if (!empty($terms_by_vid)) {
  478. foreach ($vids as $vid) {
  479. if (!empty($terms_by_vid[$vid])) {
  480. foreach ($terms_by_vid[$vid] as $tid => $term) {
  481. $tids[$tid] = $tid;
  482. }
  483. }
  484. }
  485. }
  486. // The logic above should have all terms already, but just in case we use
  487. // the "original" logic below. The taxonomy module stopped writing to the
  488. // taxonomy_index for unpublished nodes, so this works only for published
  489. // nodes.
  490. $query = db_select('taxonomy_index', 'r');
  491. $t_alias = $query->join('taxonomy_term_data', 't', 'r.tid = t.tid');
  492. $v_alias = $query->join('taxonomy_vocabulary', 'v', 't.vid = v.vid');
  493. $query->fields( $t_alias );
  494. $query->condition("r.nid", $node->nid);
  495. $query->condition("t.vid", $vids, 'IN');
  496. $result = $query->execute();
  497. foreach ($result as $term) {
  498. if (empty($tids[$term->tid])) {
  499. watchdog('tac_lite', 'Unexpected term id %tid associated with !node. Please report this to !url.', array(
  500. '%tid' => $term->tid,
  501. '!node' => l($node->title, 'node/' . $node->nid),
  502. '!url' => 'https://drupal.org/node/1918272',
  503. ), WATCHDOG_DEBUG);
  504. }
  505. $tids[$term->tid] = $term->tid;
  506. }
  507. }
  508. elseif (user_access('administer tac_lite')) {
  509. drupal_set_message(t('tac_lite.module enabled, but not <a href=!admin_url>configured</a>. No tac_lite terms associated with %title.', array(
  510. '!admin_url' => url('admin/config/people/tac_lite'),
  511. '%title' => $node->title,
  512. )));
  513. }
  514. return $tids;
  515. }
  516. /**
  517. * In Drupal 6.x, there was taxonomy_node_get_terms(). Drupal 7.x should
  518. * provide the same feature, but doesn't. Here is our workaround, based on
  519. * https://drupal.org/comment/5573176#comment-5573176.
  520. *
  521. * We organize our data structure by vid and tid.
  522. */
  523. function tac_lite_node_get_terms($node) {
  524. $terms = &drupal_static(__FUNCTION__);
  525. if (!isset($terms[$node->nid])) {
  526. // Get tids from all taxonomy_term_reference fields.
  527. $fields = field_info_fields();
  528. foreach ($fields as $field_name => $field) {
  529. // Our goal is to get all terms, regardless of language, associated with the node. Does the code below do that?
  530. if ($field['type'] == 'taxonomy_term_reference' && field_info_instance('node', $field_name, $node->type)) {
  531. if (($items = field_get_items('node', $node, $field_name)) && is_array($items)) {
  532. foreach ($items as $item) {
  533. // Sometimes $item contains only tid, sometimes entire term. Thanks Drupal for remaining mysterious!
  534. // We need to term to determine the vocabulary id.
  535. if (!empty($item['taxonomy_term'])) {
  536. $term = $item['taxonomy_term'];
  537. }
  538. else {
  539. $term = taxonomy_term_load($item['tid']);
  540. }
  541. if ($term) {
  542. $terms[$node->nid][$term->vid][$term->tid] = $term;
  543. }
  544. }
  545. }
  546. }
  547. }
  548. }
  549. return isset($terms[$node->nid]) ? $terms[$node->nid] : FALSE;
  550. }
  551. /**
  552. * Helper function to build a taxonomy term select element for a form.
  553. *
  554. * @param $v
  555. * A vocabulary object containing a vid and name.
  556. * @param $default_values
  557. * An array of values to use for the default_value argument for this form element.
  558. */
  559. function _tac_lite_term_select($v, $default_values = array()) {
  560. $tree = taxonomy_get_tree($v->vid);
  561. $options = array(0 => '<' . t('none') . '>');
  562. if ($tree) {
  563. foreach ($tree as $term) {
  564. $choice = new stdClass();
  565. $choice->option = array($term->tid => str_repeat('-', $term->depth) . $term->name);
  566. $options[] = $choice;
  567. }
  568. }
  569. $field_array = array(
  570. '#type' => 'select',
  571. '#title' => $v->name,
  572. '#default_value' => $default_values,
  573. '#options' => $options,
  574. '#multiple' => TRUE,
  575. '#description' => $v->description,
  576. );
  577. return $field_array;
  578. }
  579. /**
  580. * Return the term ids of terms this user is allowed to access.
  581. *
  582. * Users are granted access to terms either because of who they are,
  583. * or because of the roles they have.
  584. */
  585. function _tac_lite_user_tids($account, $scheme) {
  586. // grant id 0 is reserved for nodes which were not given a grant id when they were created. By adding 0 to the grant id, we let the user view those nodes.
  587. $grants = array(0);
  588. $config = _tac_lite_config($scheme);
  589. $realm = $config['realm'];
  590. if (isset($account->data[$realm]) && count($account->data[$realm])) {
  591. // $account->$realm is array. Keys are vids, values are array of tids within that vocabulary, to which the user has access
  592. foreach ($account->data[$realm] as $tids) {
  593. if (count($tids)) {
  594. $grants = array_merge($grants, $tids);
  595. }
  596. }
  597. }
  598. // add per-role grants in addition to per-user grants
  599. $defaults = variable_get('tac_lite_grants_scheme_' . $scheme, array());
  600. foreach ($account->roles as $rid => $role_name) {
  601. if (isset($defaults[$rid]) && count($defaults[$rid])) {
  602. foreach ($defaults[$rid] as $tids) {
  603. if (count($tids)) {
  604. $grants = array_merge($grants, $tids);
  605. }
  606. }
  607. }
  608. }
  609. // Because of some flakyness in the form API and the form we insert under
  610. // user settings, we may have a bogus entry with vid set
  611. // to ''. Here we make sure not to return that.
  612. unset($grants['']);
  613. return $grants;
  614. }
  615. /**
  616. * Implementation of hook_node_grants().
  617. *
  618. * Returns any grants which may give the user permission to perform the
  619. * requested op.
  620. */
  621. function tac_lite_node_grants($account, $op) {
  622. $grants = array();
  623. for ($i = 1; $i <= variable_get('tac_lite_schemes', 1); $i++) {
  624. $config = _tac_lite_config($i);
  625. if (in_array('grant_' . $op, $config['perms'])) {
  626. $grants[$config['realm']] = _tac_lite_user_tids($account, $i);
  627. }
  628. }
  629. if (count($grants)) {
  630. return $grants;
  631. }
  632. }
  633. /**
  634. * Implements hook_query_TAG_alter().
  635. *
  636. * Acts on queries that list terms (generally these should be tagged with 'term_access')
  637. * to remove any terms that this user should not be able to see.
  638. */
  639. function tac_lite_query_term_access_alter(QueryAlterableInterface $query) {
  640. global $user;
  641. // If this user has administer rights, don't filter
  642. if (user_access('administer tac_lite')) {
  643. return;
  644. }
  645. // Get our vocabularies and schemes from variables. Return if we have none.
  646. $vids = variable_get('tac_lite_categories', NULL);
  647. $schemes = variable_get('tac_lite_schemes', 1);
  648. if (!$vids || !count($vids) || !$schemes) {
  649. return;
  650. }
  651. // the terms this user is allowed to see
  652. $term_visibility = FALSE;
  653. $tids = array();
  654. for ($i = 1; $i <= $schemes; $i++) {
  655. $config = _tac_lite_config($i);
  656. if ($config['term_visibility']) {
  657. $tids = array_merge($tids, _tac_lite_user_tids($user, $i));
  658. $term_visibility = TRUE;
  659. }
  660. }
  661. if ($term_visibility) {
  662. // HELP: What is the proper way to find the alias of the primary table here?
  663. $primary_table = '';
  664. $t = $query->getTables();
  665. foreach($t as $key => $info) {
  666. if (!$info['join type']) {
  667. $primary_table = $info['alias'];
  668. }
  669. }
  670. // Prevent query from finding terms the current user does not have permission to see.
  671. $query->leftJoin('taxonomy_term_data', 'tac_td', $primary_table . '.tid = tac_td.tid');
  672. $or = db_or();
  673. $or->condition($primary_table . '.tid', $tids, 'IN');
  674. $or->condition('tac_td.vid', $vids, 'NOT IN');
  675. $query->condition($or);
  676. }
  677. }