taxonomy_access.module 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775
  1. <?php
  2. /**
  3. * @file
  4. * Allows administrators to specify access control for taxonomy categories.
  5. */
  6. /**
  7. * Maximum number of nodes for which to update node access within the module.
  8. *
  9. * If the number of affected nodes is greater, then node_access_needs_rebuild()
  10. * will be set instead.
  11. */
  12. define('TAXONOMY_ACCESS_MAX_UPDATE', 500);
  13. /**
  14. * Base path for module administration pages.
  15. */
  16. define('TAXONOMY_ACCESS_CONFIG', 'admin/config/people/taxonomy_access');
  17. /**
  18. * Global default.
  19. */
  20. define('TAXONOMY_ACCESS_GLOBAL_DEFAULT', 0);
  21. /**
  22. * Vocabulary default.
  23. */
  24. define('TAXONOMY_ACCESS_VOCABULARY_DEFAULT', 0);
  25. /**
  26. * 'Allow' grant value for nodes.
  27. */
  28. define('TAXONOMY_ACCESS_NODE_ALLOW', 1);
  29. /**
  30. * 'Ignore' grant value for nodes.
  31. */
  32. define('TAXONOMY_ACCESS_NODE_IGNORE', 0);
  33. /**
  34. * 'Deny' grant value for nodes.
  35. */
  36. define('TAXONOMY_ACCESS_NODE_DENY', 2);
  37. /**
  38. * 'Allow' grant value for terms.
  39. */
  40. define('TAXONOMY_ACCESS_TERM_ALLOW', 1);
  41. /**
  42. * 'Deny' grant value for terms.
  43. */
  44. define('TAXONOMY_ACCESS_TERM_DENY', 0);
  45. /**
  46. * Caches a list of all roles.
  47. *
  48. * @param string|null $permission
  49. * (optional) A string containing a permission. If set, only roles
  50. * containing that permission are returned. Defaults to NULL.
  51. *
  52. * @return array
  53. * An array of roles from user_roles().
  54. *
  55. * @todo
  56. * Replace this function once http://drupal.org/node/6463 is backported.
  57. */
  58. function _taxonomy_access_user_roles($permission = NULL) {
  59. $roles = &drupal_static(__FUNCTION__, array());
  60. if (!isset($roles[$permission])) {
  61. $roles[$permission] = user_roles(FALSE, $permission);
  62. }
  63. return $roles[$permission];
  64. }
  65. /**
  66. * Implements hook_init().
  67. */
  68. function taxonomy_access_init() {
  69. $path = drupal_get_path('module', 'taxonomy_access');
  70. drupal_add_css($path . '/taxonomy_access.css');
  71. // Register our shutdown function.
  72. drupal_register_shutdown_function('taxonomy_access_shutdown');
  73. }
  74. /**
  75. * Implements hook_theme().
  76. */
  77. function taxonomy_access_theme() {
  78. return array(
  79. 'taxonomy_access_admin_form' => array(
  80. 'render element' => 'form',
  81. 'file' => 'taxonomy_access.admin.inc',
  82. ),
  83. 'taxonomy_access_grant_table' => array(
  84. 'render element' => 'elements',
  85. 'file' => 'taxonomy_access.admin.inc',
  86. ),
  87. );
  88. }
  89. /**
  90. * Implements hook_element_info().
  91. */
  92. function taxonomy_access_element_info() {
  93. return array(
  94. 'taxonomy_access_grant_table' => array(
  95. '#theme' => 'taxonomy_access_grant_table',
  96. '#regions' => array('' => array()),
  97. ),
  98. );
  99. }
  100. /**
  101. * Implements hook_menu().
  102. */
  103. function taxonomy_access_menu() {
  104. $items = array();
  105. $items[TAXONOMY_ACCESS_CONFIG] = array(
  106. 'title' => 'Taxonomy access control',
  107. 'description' => 'Taxonomy-based access control for content',
  108. 'page callback' => 'taxonomy_access_admin',
  109. 'access arguments' => array('administer permissions'),
  110. 'file' => 'taxonomy_access.admin.inc',
  111. );
  112. $items[TAXONOMY_ACCESS_CONFIG . '/role'] = array(
  113. 'title' => 'Configure role access rules',
  114. 'description' => 'Configure taxonomy access control',
  115. 'page callback' => 'taxonomy_access_admin',
  116. 'access arguments' => array('administer permissions'),
  117. 'file' => 'taxonomy_access.admin.inc',
  118. 'type' => MENU_DEFAULT_LOCAL_TASK,
  119. );
  120. $items[TAXONOMY_ACCESS_CONFIG . '/role/%/edit'] = array(
  121. 'title callback' => 'taxonomy_access_role_edit_title',
  122. 'title arguments' => array(5),
  123. 'page callback' => 'drupal_get_form',
  124. 'page arguments' => array('taxonomy_access_admin_role', 5),
  125. 'access callback' => 'taxonomy_access_role_edit_access',
  126. 'access arguments' => array(5),
  127. 'file' => 'taxonomy_access.admin.inc',
  128. );
  129. $items[TAXONOMY_ACCESS_CONFIG . '/role/%/enable'] = array(
  130. 'page callback' => 'taxonomy_access_enable_role_validate',
  131. 'page arguments' => array(5),
  132. 'access arguments' => array('administer permissions'),
  133. 'file' => 'taxonomy_access.admin.inc',
  134. );
  135. $items[TAXONOMY_ACCESS_CONFIG . '/role/%/delete'] = array(
  136. 'page callback' => 'drupal_get_form',
  137. 'page arguments' => array('taxonomy_access_role_delete_confirm', 5),
  138. 'access callback' => 'taxonomy_access_role_delete_access',
  139. 'access arguments' => array(5),
  140. 'file' => 'taxonomy_access.admin.inc',
  141. 'type' => MENU_CALLBACK,
  142. );
  143. $items[TAXONOMY_ACCESS_CONFIG . '/role/%/disable/%taxonomy_vocabulary'] = array(
  144. 'page callback' => 'taxonomy_access_disable_vocab_confirm_page',
  145. 'page arguments' => array(5, 7),
  146. 'access arguments' => array('administer permissions'),
  147. 'file' => 'taxonomy_access.admin.inc',
  148. 'type' => MENU_CALLBACK,
  149. );
  150. $items['taxonomy_access/autocomplete'] = array(
  151. 'title' => 'Autocomplete taxonomy',
  152. 'page callback' => 'taxonomy_access_autocomplete',
  153. 'access arguments' => array('access content'),
  154. 'type' => MENU_CALLBACK,
  155. 'file' => 'taxonomy_access.create.inc',
  156. );
  157. return $items;
  158. }
  159. /**
  160. * Title callback: Returns the title for the role edit form.
  161. */
  162. function taxonomy_access_role_edit_title($rid) {
  163. $roles = _taxonomy_access_user_roles();
  164. return t('Access rules for @role', array('@role' => $roles[$rid]));
  165. }
  166. /**
  167. * Access callback: Determines whether the admin form can be accessed.
  168. */
  169. function taxonomy_access_role_edit_access($rid) {
  170. // Allow access only if the user may administer permissions.
  171. if (!user_access('administer permissions')) {
  172. return FALSE;
  173. }
  174. // Do not render the form for invalid role IDs.
  175. $roles = _taxonomy_access_user_roles();
  176. if (empty($roles[$rid])) {
  177. return FALSE;
  178. }
  179. // If the conditions above are met, grant access.
  180. return TRUE;
  181. }
  182. /**
  183. * Access callback for role deletion form.
  184. */
  185. function taxonomy_access_role_delete_access($rid) {
  186. if (!user_access('administer permissions')) {
  187. return FALSE;
  188. }
  189. if (($rid == DRUPAL_ANONYMOUS_RID) || ($rid == DRUPAL_AUTHENTICATED_RID)) {
  190. return FALSE;
  191. }
  192. $roles = _taxonomy_access_user_roles();
  193. if (empty($roles[$rid])) {
  194. return FALSE;
  195. }
  196. return TRUE;
  197. }
  198. /**
  199. * Implements hook_user_role_delete().
  200. */
  201. function taxonomy_access_user_role_delete($role) {
  202. // Do not update node access since the role will no longer exist.
  203. taxonomy_access_delete_role_grants($role->rid, FALSE);
  204. }
  205. /**
  206. * Implements hook_taxonomy_vocabulary_delete().
  207. */
  208. function taxonomy_access_taxonomy_vocabulary_delete($vocab) {
  209. taxonomy_access_delete_default_grants($vocab->vid);
  210. }
  211. /**
  212. * Implements hook_taxonomy_term_delete().
  213. */
  214. function taxonomy_access_taxonomy_term_delete($term) {
  215. taxonomy_access_delete_term_grants($term->tid);
  216. }
  217. /**
  218. * Implements hook_node_grants().
  219. *
  220. * Gives access to taxonomies based on the taxonomy_access table.
  221. */
  222. function taxonomy_access_node_grants($user, $op) {
  223. $roles = is_array($user->roles) ? array_keys($user->roles) : -1;
  224. return array('taxonomy_access_role' => $roles);
  225. }
  226. /**
  227. * Implements hook_node_access_records().
  228. *
  229. * @ingroup tac_node_access
  230. */
  231. function taxonomy_access_node_access_records($node) {
  232. // Only write grants for published nodes.
  233. if ($node->status) {
  234. // Make sure to reset caches for accurate grant values.
  235. return _taxonomy_access_node_access_records($node->nid, TRUE);
  236. }
  237. }
  238. /**
  239. * Implements hook_field_info_alter().
  240. *
  241. * @todo
  242. * Should we somehow pass the originl callback to our callback dynamically?
  243. */
  244. function taxonomy_access_field_info_alter(&$info) {
  245. // Return if there's no term reference field type.
  246. if (empty($info['taxonomy_term_reference'])) {
  247. return;
  248. }
  249. // Use our custom callback in order to disable list while generating options.
  250. $info['taxonomy_term_reference']['settings']['options_list_callback'] = '_taxonomy_access_term_options';
  251. }
  252. /**
  253. * Implements hook_field_attach_validate().
  254. *
  255. * For form validation:
  256. * @see taxonomy_access_options_validate()
  257. * @see taxonomy_access_autocomplete_validate()
  258. */
  259. function taxonomy_access_field_attach_validate($entity_type, $entity, &$errors) {
  260. // Add create grant handling.
  261. module_load_include('inc', 'taxonomy_access', 'taxonomy_access.create');
  262. _taxonomy_access_field_validate($entity_type, $entity, $errors);
  263. }
  264. /**
  265. * Implements hook_query_TAG_alter() for 'term_access'.
  266. *
  267. * Provides sitewide list grant filtering, as well as create grant filtering
  268. * for autocomplete paths.
  269. *
  270. * @todo
  271. * Fix create permission filtering for autocomplete paths.
  272. *
  273. * @ingroup tac_list
  274. */
  275. function taxonomy_access_query_term_access_alter($query) {
  276. // Take no action while the list op is disabled.
  277. if (!taxonomy_access_list_enabled()) {
  278. return;
  279. }
  280. // Take no action if there is no term table in the query.
  281. $alias = '';
  282. $tables =& $query->getTables();
  283. foreach ($tables as $i => $table) {
  284. if (strpos($table['table'], 'taxonomy_term_') === 0) {
  285. $alias = $table['alias'];
  286. }
  287. }
  288. if (empty($alias)) {
  289. return;
  290. }
  291. // Fetch a list of all terms the user may list.
  292. $tids = &drupal_static(__FUNCTION__, taxonomy_access_user_list_terms());
  293. // If exactly TRUE was returned, the user can list all terms.
  294. if ($tids === TRUE) {
  295. return;
  296. }
  297. // If the user cannot list any terms, then allow only null values.
  298. if (empty($tids)) {
  299. $query->isNull($alias . ".tid");
  300. }
  301. // Otherwise, filter to the terms provided.
  302. else {
  303. $query->condition($alias . ".tid", $tids, "IN");
  304. }
  305. }
  306. /**
  307. * Implements hook_field_widget_WIDGET_TYPE_form_alter().
  308. *
  309. * @see _taxonomy_access_autocomplete_alter()
  310. */
  311. function taxonomy_access_field_widget_taxonomy_autocomplete_form_alter(&$element, &$form_state, $context) {
  312. // Enforce that list grants do not filter the autocomplete.
  313. taxonomy_access_disable_list();
  314. // Add create grant handling.
  315. module_load_include('inc', 'taxonomy_access', 'taxonomy_access.create');
  316. _taxonomy_access_autocomplete_alter($element, $form_state, $context);
  317. // Re-enable list grants.
  318. taxonomy_access_enable_list();
  319. }
  320. /**
  321. * Implements hook_field_widget_form_alter().
  322. *
  323. * @see _taxonomy_access_options_alter()
  324. */
  325. function taxonomy_access_field_widget_form_alter(&$element, &$form_state, $context) {
  326. // Only act on taxonomy fields.
  327. if ($context['field']['type'] != 'taxonomy_term_reference') {
  328. return;
  329. }
  330. // Only act on options widgets.
  331. $widget = $context['instance']['widget']['type'];
  332. if (!in_array($widget, array('options_buttons', 'options_select'))) {
  333. return;
  334. }
  335. // Enforce that list grants do not filter our queries.
  336. taxonomy_access_disable_list();
  337. // Add create grant handling.
  338. module_load_include('inc', 'taxonomy_access', 'taxonomy_access.create');
  339. _taxonomy_access_options_alter($element, $form_state, $context);
  340. // Re-enable list grants.
  341. taxonomy_access_enable_list();
  342. }
  343. /**
  344. * Enables access control for a given role.
  345. *
  346. * @param int $rid
  347. * The role ID.
  348. *
  349. * @return bool
  350. * TRUE on success, or FALSE on failure.
  351. *
  352. * @todo
  353. * Should we default to the authenticated user global default?
  354. */
  355. function taxonomy_access_enable_role($rid) {
  356. $rid = intval($rid);
  357. // Take no action if the role is already enabled. All valid role IDs are > 0.
  358. if (!$rid || taxonomy_access_role_enabled($rid)) {
  359. return FALSE;
  360. }
  361. // If we are adding a role, no global default is set yet, so insert it now.
  362. // Assemble a $row object for Schema API.
  363. $row = new stdClass();
  364. $row->vid = TAXONOMY_ACCESS_GLOBAL_DEFAULT;
  365. $row->rid = $rid;
  366. // Insert the row with defaults for all grants.
  367. return drupal_write_record('taxonomy_access_default', $row);
  368. }
  369. /**
  370. * Indicates whether access control is enabled for a given role.
  371. *
  372. * @param int $rid
  373. * The role ID.
  374. *
  375. * @return bool
  376. * TRUE if access control is enabled for the role, or FALSE otherwise.
  377. */
  378. function taxonomy_access_role_enabled($rid) {
  379. $role_status = &drupal_static(__FUNCTION__, array());
  380. if (!isset($role_status[$rid])) {
  381. $role_status[$rid] =
  382. db_query(
  383. 'SELECT 1
  384. FROM {taxonomy_access_default}
  385. WHERE rid = :rid AND vid = :vid',
  386. array(':rid' => $rid, ':vid' => TAXONOMY_ACCESS_GLOBAL_DEFAULT))
  387. ->fetchField();
  388. }
  389. return (bool) $role_status[$rid];
  390. }
  391. /**
  392. * Enables a vocabulary for the given role.
  393. *
  394. * @param int $vid
  395. * The vocabulary ID to enable.
  396. * @param int $rid
  397. * The role ID.
  398. *
  399. * @return bool
  400. * TRUE on success, or FALSE on failure.
  401. *
  402. * @see taxnomomy_access_enable_role()
  403. */
  404. function taxonomy_access_enable_vocab($vid, $rid) {
  405. $rid = intval($rid);
  406. $vid = intval($vid);
  407. // All valid role IDs are > 0, and we do not enable the global default here.
  408. if (!$rid || !$vid) {
  409. return FALSE;
  410. }
  411. // Take no action if the vocabulary is already enabled for the role.
  412. $vocab_status =
  413. db_query(
  414. 'SELECT 1
  415. FROM {taxonomy_access_default}
  416. WHERE rid = :rid AND vid = :vid',
  417. array(':rid' => $rid, ':vid' => $vid))
  418. ->fetchField();
  419. if ($vocab_status) {
  420. return FALSE;
  421. }
  422. // Otherwise, initialize the vocabulary default with the global default.
  423. // Use our API functions so that node access gets updated as needed.
  424. $global_default =
  425. db_query(
  426. 'SELECT grant_view, grant_update, grant_delete, grant_create, grant_list
  427. FROM {taxonomy_access_default}
  428. WHERE vid = :vid AND rid = :rid',
  429. array(':rid' => $rid, ':vid' => TAXONOMY_ACCESS_GLOBAL_DEFAULT))
  430. ->fetchAssoc();
  431. $record = _taxonomy_access_format_grant_record($vid, $rid, $global_default, TRUE);
  432. return taxonomy_access_set_default_grants(array($vid => $record));
  433. }
  434. /**
  435. * Disables a vocabulary for the given role.
  436. *
  437. * @param int $vid
  438. * The vocabulary ID to enable.
  439. * @param int $rid
  440. * The role ID.
  441. *
  442. * @return bool
  443. * TRUE on success, or FALSE on failure.
  444. *
  445. * @see taxonomy_access_delete_role_grants()
  446. */
  447. function taxonomy_access_disable_vocab($vid, $rid) {
  448. $rid = intval($rid);
  449. $vid = intval($vid);
  450. // Do not allow the global default to be deleted this way.
  451. // Deleting the global default would disable the role.
  452. if (!$vid || !$rid) {
  453. return FALSE;
  454. }
  455. // Delete the vocabulary default.
  456. taxonomy_access_delete_default_grants($vid, $rid);
  457. // Delete the role's term access rules for the vocabulary.
  458. // First check which term records are enabled so we can update node access.
  459. $tids =
  460. db_query(
  461. "SELECT ta.tid
  462. FROM {taxonomy_access_term} ta
  463. INNER JOIN {taxonomy_term_data} td ON ta.tid = td.tid
  464. WHERE td.vid = :vid AND ta.rid = :rid",
  465. array(':vid' => $vid, ':rid' => $rid))
  466. ->fetchCol();
  467. taxonomy_access_delete_term_grants($tids, $rid);
  468. return TRUE;
  469. }
  470. /**
  471. * @defgroup tac_affected_nodes Taxonomy Access Control: Node access update mechanism
  472. * @{
  473. * Update node access on shutdown in response to other changes.
  474. */
  475. /**
  476. * Shutdown function: Performs any needed node access updates.
  477. *
  478. * @see taxonomy_access_init()
  479. */
  480. function taxonomy_access_shutdown() {
  481. // Update any affected nodes.
  482. $affected_nodes = taxonomy_access_affected_nodes();
  483. if (!empty($affected_nodes)) {
  484. taxonomy_access_affected_nodes(NULL, TRUE);
  485. _taxonomy_access_node_access_update($affected_nodes);
  486. }
  487. }
  488. /**
  489. * Flags node access for rebuild with a message for administrators.
  490. */
  491. function _taxonomy_access_flag_rebuild() {
  492. drupal_set_message(t("Taxonomy Access Control is updating node access... If you see a message that content access permissions need to be rebuilt, you may wait until after you have completed your configuration changes."), 'status');
  493. node_access_needs_rebuild(TRUE);
  494. }
  495. /**
  496. * Updates node access grants for a set of nodes.
  497. *
  498. * @param array $nids
  499. * An array of node IDs for which to acquire access permissions.
  500. *
  501. * @todo
  502. * Unset rebuild message when we set the flag to false?
  503. */
  504. function _taxonomy_access_node_access_update(array $nids) {
  505. // Proceed only if node_access_needs_rebuild() is not already flagged.
  506. if (!node_access_needs_rebuild()) {
  507. // Set node_access_needs_rebuild() until we succeed below.
  508. _taxonomy_access_flag_rebuild();
  509. // Remove any duplicate nids from the array.
  510. $nids = array_unique($nids);
  511. // If the number of nodes is small enough, update node access for each.
  512. if (sizeof($nids) < TAXONOMY_ACCESS_MAX_UPDATE) {
  513. foreach ($nids as $node) {
  514. $loaded_node = node_load($node, NULL, TRUE);
  515. if (!empty($loaded_node)) {
  516. node_access_acquire_grants($loaded_node);
  517. }
  518. }
  519. // If we make it here our update was successful; unflag rebuild.
  520. node_access_needs_rebuild(FALSE);
  521. }
  522. }
  523. return TRUE;
  524. }
  525. /**
  526. * Caches and retrieves nodes affected by a taxonomy change.
  527. *
  528. * @param array $affected_nodes
  529. * (optional) If we are caching, the list of nids to cache.
  530. * Defaults to NULL.
  531. * @param bool $reset
  532. * (optional) Flag to manually reset the list. Defaults to FALSE.
  533. *
  534. * @return
  535. * The cached list of nodes.
  536. */
  537. function taxonomy_access_affected_nodes(array $affected_nodes = NULL, $reset = FALSE) {
  538. static $nodes = array();
  539. // If node_access_needs_rebuild or $reset are set, reset list and return.
  540. if (!empty($nodes)) {
  541. if (node_access_needs_rebuild() || $reset) {
  542. $nodes = array();
  543. return;
  544. }
  545. }
  546. // If we were passed a list of nodes, cache.
  547. if (isset($affected_nodes)) {
  548. $nodes = array_unique(array_merge($nodes, $affected_nodes));
  549. // Stop caching if there are more nodes than the limit.
  550. if (sizeof($nodes) >= TAXONOMY_ACCESS_MAX_UPDATE) {
  551. _taxonomy_access_flag_rebuild();
  552. unset($nodes);
  553. }
  554. }
  555. // Otherwise, return the cached data.
  556. else {
  557. return $nodes;
  558. }
  559. }
  560. /**
  561. * Gets node IDs with controlled terms or vocabs for any of the given roles.
  562. *
  563. * @param int $rid
  564. * A single role ID.
  565. *
  566. * @return array
  567. * An array of node IDs associated with terms or vocabularies that are
  568. * controlled for the role.
  569. */
  570. function _taxonomy_access_get_controlled_nodes_for_role($rid) {
  571. $query = db_select('taxonomy_index', 'ti')
  572. ->fields('ti', array('nid'))
  573. ->addTag('taxonomy_access_node');
  574. $query->leftJoin('taxonomy_term_data', 'td', 'ti.tid = td.tid');
  575. $query->leftJoin('taxonomy_access_term', 'ta', 'ti.tid = ta.tid');
  576. $query->leftJoin('taxonomy_access_default', 'tad', 'tad.vid = td.vid');
  577. // The query builder will automatically use = or IN() as appropriate.
  578. $query->condition(
  579. db_or()
  580. ->condition('ta.rid', $rid)
  581. ->condition('tad.rid', $rid)
  582. );
  583. $nids = $query->execute()->fetchCol();
  584. return $nids;
  585. }
  586. /**
  587. * Gets node IDs associated with the roles' global defaults.
  588. *
  589. * @param int $rid
  590. * A single role ID.
  591. *
  592. * @return array
  593. * An array of node IDs associated with the global default.
  594. */
  595. function _taxonomy_access_get_nodes_for_global_default($rid) {
  596. // Two kinds of nodes are governed by the global default:
  597. // 1. Nodes with terms controlled neither directly nor by vocab. defaults,
  598. // 2. Nodes with no terms.
  599. // Get a list of all terms controlled for the role, either directly or
  600. // by a vocabulary default.
  601. $tids = _taxonomy_access_global_controlled_terms($rid);
  602. $query =
  603. db_select('node', 'n')
  604. ->fields('n', array('nid'))
  605. ->addTag('taxonomy_access_node')
  606. ;
  607. // With a left join, the term ID for untagged nodes will be NULL.
  608. if (!empty($tids)) {
  609. $query->leftJoin('taxonomy_index', 'ti', 'ti.nid = n.nid');
  610. $query->condition(
  611. db_or()
  612. ->condition('ti.tid', $tids, 'NOT IN')
  613. ->isNull('ti.tid')
  614. );
  615. }
  616. $nids = $query->execute()->fetchCol();
  617. return $nids;
  618. }
  619. /**
  620. * Gets node IDs associated with a given vocabulary.
  621. *
  622. * @param int|array $vocab_ids
  623. * A single vocabulary ID or an array of IDs.
  624. * @param int $rid.
  625. * (optional) A single role ID.
  626. * This argument has the effect of filtering out nodes in terms that
  627. * are already controlled invidually for the role. Defaults to NULL.
  628. *
  629. * @return array
  630. * An array of node IDs associated with the given vocabulary.
  631. */
  632. function _taxonomy_access_get_nodes_for_defaults($vocab_ids, $rid = NULL) {
  633. // Accept either a single vocabulary ID or an array thereof.
  634. if (is_numeric($vocab_ids)) {
  635. $vocab_ids = array($vocab_ids);
  636. }
  637. if (empty($vocab_ids)) {
  638. return FALSE;
  639. }
  640. // If a role was passed, get terms controlled for that role.
  641. if (!empty($rid)) {
  642. $tids = _taxonomy_access_vocab_controlled_terms($vocab_ids, $rid);
  643. }
  644. $query =
  645. db_select('taxonomy_index', 'ti')
  646. ->condition('td.vid', $vocab_ids)
  647. ->fields('ti', array('nid'))
  648. ->addTag('taxonomy_access_node');
  649. ;
  650. $query->join('taxonomy_term_data', 'td', 'td.tid = ti.tid');
  651. // Exclude records with controlled terms from the results.
  652. if (!empty($tids)) {
  653. $query->condition('ti.tid', $tids, 'NOT IN');
  654. }
  655. $nids = $query->execute()->fetchCol();
  656. unset($tids);
  657. unset($query);
  658. // If the global default is in the list, fetch those nodes as well.
  659. if (in_array(TAXONOMY_ACCESS_GLOBAL_DEFAULT, $vocab_ids)) {
  660. $nids =
  661. array_merge($nids, _taxonomy_access_get_nodes_for_global_default($rid));
  662. }
  663. return $nids;
  664. }
  665. /**
  666. * Retrieves a list of terms controlled by the global default for a role.
  667. *
  668. * @param int $rid
  669. * The role ID.
  670. *
  671. * @return array
  672. * A list of term IDs.
  673. */
  674. function _taxonomy_access_global_controlled_terms($rid) {
  675. $tids =
  676. db_query(
  677. "SELECT td.tid
  678. FROM {taxonomy_term_data} td
  679. LEFT JOIN {taxonomy_access_term} ta ON td.tid = ta.tid
  680. LEFT JOIN {taxonomy_access_default} tad ON td.vid = tad.vid
  681. WHERE ta.rid = :rid OR tad.rid = :rid",
  682. array(':rid' => $rid)
  683. )
  684. ->fetchCol();
  685. return $tids;
  686. }
  687. /**
  688. * Retrieves a list of terms controlled by the global default for a role.
  689. *
  690. * @param int $rid
  691. * The role ID.
  692. *
  693. * @return array
  694. * A list of term IDs.
  695. */
  696. function _taxonomy_access_vocab_controlled_terms($vids, $rid) {
  697. // Accept either a single vocabulary ID or an array thereof.
  698. if (is_numeric($vids)) {
  699. $vids = array($vids);
  700. }
  701. $tids =
  702. db_query(
  703. "SELECT td.tid
  704. FROM {taxonomy_term_data} td
  705. INNER JOIN {taxonomy_access_term} ta ON td.tid = ta.tid
  706. WHERE ta.rid = :rid
  707. AND td.vid IN (:vids)",
  708. array(':rid' => $rid, ':vids' => $vids)
  709. )
  710. ->fetchCol();
  711. return $tids;
  712. }
  713. /**
  714. * Gets node IDs associated with a given term.
  715. *
  716. * @param int|array $term_ids
  717. * A single term ID or an array of term IDs.
  718. *
  719. * @return array
  720. * An array of node IDs associated with the given terms.
  721. */
  722. function _taxonomy_access_get_nodes_for_terms($term_ids) {
  723. if (empty($term_ids)) {
  724. return FALSE;
  725. }
  726. // The query builder will use = or IN() automatically as appropriate.
  727. $nids =
  728. db_select('taxonomy_index', 'ti')
  729. ->condition('ti.tid', $term_ids)
  730. ->fields('ti', array('nid'))
  731. ->addTag('taxonomy_access_node')
  732. ->execute()
  733. ->fetchCol();
  734. unset($term_ids);
  735. return $nids;
  736. }
  737. /**
  738. * Gets term IDs for all descendants of the given term.
  739. *
  740. * @param int $tid
  741. * The term ID for which to fetch children.
  742. *
  743. * @return array
  744. * An array of the IDs of the term's descendants.
  745. */
  746. function _taxonomy_access_get_descendants($tid) {
  747. $descendants = &drupal_static(__FUNCTION__, array());
  748. if (!isset($descendants[$tid])) {
  749. // Preserve the original state of the list flag.
  750. $flag_state = taxonomy_access_list_enabled();
  751. // Enforce that list grants do not filter the results.
  752. taxonomy_access_disable_list();
  753. $descendants[$tid] = array();
  754. $term = taxonomy_term_load($tid);
  755. $tree = taxonomy_get_tree($term->vid, $tid);
  756. foreach ($tree as $term) {
  757. $descendants[$tid][] = $term->tid;
  758. }
  759. // Restore list flag to previous state.
  760. if ($flag_state) {
  761. taxonomy_access_enable_list();
  762. }
  763. unset($term);
  764. unset($tree);
  765. }
  766. return $descendants[$tid];
  767. }
  768. /**
  769. * Gets term IDs for all terms in the vocabulary
  770. *
  771. * @param int $vocab_id
  772. * The vocabulary ID for which to fetch children.
  773. *
  774. * @return array
  775. * An array of the IDs of the terms in in the vocabulary.
  776. */
  777. function _taxonomy_access_get_vocabulary_terms($vocab_id) {
  778. static $descendants = array();
  779. if (!isset($descendants[$vocab_id])) {
  780. // Preserve the original state of the list flag.
  781. $flag_state = taxonomy_access_list_enabled();
  782. // Enforce that list grants do not filter the results.
  783. taxonomy_access_disable_list();
  784. $descendants[$vocab_id] = array();
  785. $tree = taxonomy_get_tree($vocab_id);
  786. foreach ($tree as $term) {
  787. $descendants[$vocab_id][] = $term->tid;
  788. }
  789. // Restore list flag to previous state.
  790. if ($flag_state) {
  791. taxonomy_access_enable_list();
  792. }
  793. unset($term);
  794. unset($tree);
  795. }
  796. return $descendants[$vocab_id];
  797. }
  798. /**
  799. * End of "defgroup tac_affected_nodes".
  800. * @}
  801. */
  802. /**
  803. * @defgroup tac_grant_api Taxonomy Access Control: Grant record API
  804. * @{
  805. * Store, retrieve, and delete module access rules for terms and vocabularies.
  806. */
  807. /**
  808. * Deletes module configurations for the given role IDs.
  809. *
  810. * @param int $rid
  811. * A single role ID.
  812. * @param bool $update_nodes
  813. * (optional) A flag to determine whether nodes should be queued for update.
  814. * Defaults to TRUE.
  815. *
  816. * @return bool
  817. * TRUE on success, or FALSE on failure.
  818. */
  819. function taxonomy_access_delete_role_grants($rid, $update_nodes = TRUE) {
  820. if (empty($rid)) {
  821. return FALSE;
  822. }
  823. if ($rid == DRUPAL_ANONYMOUS_RID || $rid == DRUPAL_AUTHENTICATED_RID) {
  824. return FALSE;
  825. }
  826. if ($update_nodes) {
  827. // Cache the list of nodes that will be affected by this change.
  828. // Affected nodes will be those tied to configurations that are more
  829. // permissive than those from the authenticated user role.
  830. // If any global defaults are more permissive, we need to update all nodes.
  831. // Fetch global defaults.
  832. $global_defaults = taxonomy_access_global_defaults();
  833. $gd_records = array();
  834. foreach ($global_defaults as $row) {
  835. $gd_records[] = _taxonomy_access_format_node_access_record($row);
  836. }
  837. // Find the ones we need.
  838. foreach ($gd_records as $gd) {
  839. if ($gd['gid'] == DRUPAL_AUTHENTICATED_RID) {
  840. $auth_gd = $gd;
  841. }
  842. elseif ($gd['gid'] == $rid) {
  843. $role_gd = $gd;
  844. }
  845. }
  846. // Check node grants for the global default.
  847. // If any is more permissive, flag that we need to update all nodes.
  848. $all_nodes = FALSE;
  849. foreach (array('grant_view', 'grant_update', 'grant_delete') as $op) {
  850. switch ($auth_gd[$op]) {
  851. // If the authenticated user has a Deny grant, then either Allow or
  852. // Ignore for the role is more permissive.
  853. case TAXONOMY_ACCESS_NODE_DENY:
  854. if (($role_gd[$op] == TAXONOMY_ACCESS_NODE_IGNORE) || ($role_gd[$op] == TAXONOMY_ACCESS_NODE_ALLOW)){
  855. $all_nodes = TRUE;
  856. }
  857. break 2;
  858. // If the authenticated user has Ignore, Allow is more permissive.
  859. case TAXONOMY_ACCESS_NODE_IGNORE:
  860. if ($role_gd[$op] == TAXONOMY_ACCESS_NODE_ALLOW) {
  861. $all_nodes = TRUE;
  862. }
  863. break 2;
  864. }
  865. }
  866. // If flagged, add all nodes to the affected nodes cache.
  867. if ($all_nodes) {
  868. $affected_nodes = db_query('SELECT nid FROM {node}')->fetchCol();
  869. }
  870. // Otherwise, just get nodes controlled by specific configurations.
  871. else {
  872. $affected_nodes =
  873. _taxonomy_access_get_controlled_nodes_for_role($rid);
  874. }
  875. taxonomy_access_affected_nodes($affected_nodes);
  876. unset($affected_nodes);
  877. }
  878. db_delete('taxonomy_access_term')
  879. ->condition('rid', $rid)
  880. ->execute();
  881. db_delete('taxonomy_access_default')
  882. ->condition('rid', $rid)
  883. ->execute();
  884. return TRUE;
  885. }
  886. /**
  887. * Deletes module configurations for the given vocabulary IDs.
  888. *
  889. * @param int|array $vocab_ids
  890. * A single vocabulary ID or an array of vocabulary IDs.
  891. * @param int|null $rid
  892. * (optional) A single role ID. Defaults to NULL.
  893. * @param bool $update_nodes
  894. * (optional) A flag to determine whether nodes should be queued for update.
  895. * Defaults to TRUE.
  896. *
  897. * @return bool
  898. * TRUE on success, or FALSE on failure.
  899. */
  900. function taxonomy_access_delete_default_grants($vocab_ids, $rid = NULL, $update_nodes = TRUE) {
  901. // Accept either a single vocabulary ID or an array thereof.
  902. if ($vocab_ids !== TAXONOMY_ACCESS_GLOBAL_DEFAULT && empty($vocab_ids)) {
  903. return FALSE;
  904. }
  905. if ($update_nodes) {
  906. // Cache the list of nodes that will be affected by this change.
  907. $affected_nodes =
  908. _taxonomy_access_get_nodes_for_defaults($vocab_ids, $rid);
  909. taxonomy_access_affected_nodes($affected_nodes);
  910. unset($affected_nodes);
  911. }
  912. // The query builder will use = or IN() automatically as appropriate.
  913. $query =
  914. db_delete('taxonomy_access_default')
  915. ->condition('vid', $vocab_ids);
  916. if (!empty($rid)) {
  917. $query->condition('rid', $rid);
  918. }
  919. $query->execute();
  920. unset($query);
  921. return TRUE;
  922. }
  923. /**
  924. * Deletes module configurations for the given term IDs.
  925. *
  926. * @param int|array $term_ids
  927. * A single term ID or an array of term IDs.
  928. * @param int|null $rid
  929. * (optional) A single role ID. Defaults to NULL.
  930. * @param bool $update_nodes
  931. * (optional) A flag to determine whether nodes should be queued for update.
  932. * Defaults to TRUE.
  933. *
  934. * @return bool
  935. * TRUE on success, or FALSE on failure.
  936. */
  937. function taxonomy_access_delete_term_grants($term_ids, $rid = NULL, $update_nodes = TRUE) {
  938. // Accept either a single term ID or an array thereof.
  939. if (is_numeric($term_ids)) {
  940. $term_ids = array($term_ids);
  941. }
  942. if (empty($term_ids)) {
  943. return FALSE;
  944. }
  945. if ($update_nodes) {
  946. // Cache the list of nodes that will be affected by this change.
  947. $affected_nodes = _taxonomy_access_get_nodes_for_terms($term_ids);
  948. taxonomy_access_affected_nodes($affected_nodes);
  949. unset($affected_nodes);
  950. }
  951. // Delete our database records for these terms.
  952. $query =
  953. db_delete('taxonomy_access_term')
  954. ->condition('tid', $term_ids);
  955. if (!empty($rid)) {
  956. $query->condition('rid', $rid);
  957. }
  958. $query->execute();
  959. unset($term_ids);
  960. unset($query);
  961. return TRUE;
  962. }
  963. /**
  964. * Formats a record to be written to the module's configuration tables.
  965. *
  966. * @param int $id
  967. * The term or vocabulary ID.
  968. * @param int $rid
  969. * The role ID.
  970. * @param array $grants
  971. * An array of grants to write, in the format grant_name => value.
  972. * Allowed keys:
  973. * - 'view' or 'grant_view'
  974. * - 'update' or 'grant_update'
  975. * - 'delete' or 'grant_delete'
  976. * - 'create' or 'grant_create'
  977. * - 'list' or 'grant_list'
  978. * @param bool $default
  979. * (optional) Whether this is a term record (FALSE) or default record (TRUE).
  980. * Defaults to FALSE.
  981. *
  982. * @return object
  983. * A grant row object formatted for Schema API.
  984. */
  985. function _taxonomy_access_format_grant_record($id, $rid, array $grants, $default = FALSE) {
  986. $row = new stdClass();
  987. if ($default) {
  988. $row->vid = $id;
  989. }
  990. else {
  991. $row->tid = $id;
  992. }
  993. $row->rid = $rid;
  994. foreach ($grants as $op => $value) {
  995. if (is_numeric($value)) {
  996. $grant_name = strpos($op, 'grant_') ? $op : "grant_$op";
  997. $row->$grant_name = $value;
  998. }
  999. }
  1000. return $row;
  1001. }
  1002. /**
  1003. * Updates term grants for a role.
  1004. *
  1005. * @param array $grant_rows
  1006. * An array of grant row objects formatted for Schema API, keyed by term ID.
  1007. * @param bool $update_nodes
  1008. * (optional) A flag indicating whether to update node access.
  1009. * Defaults to TRUE.
  1010. *
  1011. * @return bool
  1012. * TRUE on success, or FALSE on failure.
  1013. *
  1014. * @see _taxonomy_access_format_grant_record()
  1015. */
  1016. function taxonomy_access_set_term_grants(array $grant_rows, $update_nodes = TRUE) {
  1017. // Collect lists of term and role IDs in the list.
  1018. $terms_for_roles = array();
  1019. foreach ($grant_rows as $grant_row) {
  1020. $terms_for_roles[$grant_row->rid][] = $grant_row->tid;
  1021. }
  1022. // Delete existing records for the roles and terms.
  1023. // This will also cache a list of the affected nodes.
  1024. foreach ($terms_for_roles as $rid => $tids) {
  1025. taxonomy_access_delete_term_grants($tids, $rid, $update_nodes);
  1026. }
  1027. // Insert new entries.
  1028. foreach ($grant_rows as $row) {
  1029. drupal_write_record('taxonomy_access_term', $row);
  1030. }
  1031. // Later we will refactor; for now return TRUE when this is called.
  1032. return TRUE;
  1033. }
  1034. /**
  1035. * Updates vocabulary default grants for a role.
  1036. *
  1037. * @param $rid
  1038. * The role ID to add the permission for.
  1039. * @param (array) $grant_rows
  1040. * An array of grant rows formatted for Schema API, keyed by vocabulary ID.
  1041. * @param $update_nodes
  1042. * (optional) A flag indicating whether to update node access.
  1043. * Defaults to TRUE.
  1044. *
  1045. * @return bool
  1046. * TRUE on success, or FALSE on failure.
  1047. *
  1048. * @see _taxonomy_access_format_grant_record()
  1049. */
  1050. function taxonomy_access_set_default_grants(array $grant_rows, $update_nodes = TRUE) {
  1051. // Collect lists of term and role IDs in the list.
  1052. $vocabs_for_roles = array();
  1053. foreach ($grant_rows as $grant_row) {
  1054. $vocabs_for_roles[$grant_row->rid][] = $grant_row->vid;
  1055. }
  1056. // Delete existing records for the roles and vocabularies.
  1057. // This will also cache a list of the affected nodes.
  1058. foreach ($vocabs_for_roles as $rid => $vids) {
  1059. taxonomy_access_delete_default_grants($vids, $rid, $update_nodes);
  1060. }
  1061. // Insert new entries.
  1062. foreach ($grant_rows as $row) {
  1063. drupal_write_record('taxonomy_access_default', $row);
  1064. }
  1065. // Later we will refactor; for now return TRUE when this is called.
  1066. return TRUE;
  1067. }
  1068. /**
  1069. * End of "defgroup tac_grant_api".
  1070. * @}
  1071. */
  1072. /**
  1073. * @defgroup tac_node_access Taxonomy Access Control: Node access implementation
  1074. * @{
  1075. * Functions to set node access based on configured access rules.
  1076. */
  1077. /**
  1078. * Builds a base query object for the specified TAC grants.
  1079. *
  1080. * Callers should add conditions, groupings, and optionally fields.
  1081. *
  1082. * This query should work on D7's supported versions of MySQL and PostgreSQL;
  1083. * patches may be needed for other databases. We add query tags to allow
  1084. * other systems to manipulate the query as needed.
  1085. *
  1086. * @param array $grants
  1087. * Grants to select.
  1088. * Allowed values: 'view', 'update', 'delete', 'create', 'list'
  1089. * @param bool $default
  1090. * (optional) Flag to select default grants only. Defaults to FALSE.
  1091. *
  1092. * @return object
  1093. * Query object.
  1094. */
  1095. function _taxonomy_access_grant_query(array $grants, $default = FALSE) {
  1096. $table = $default ? 'taxonomy_vocabulary' : 'taxonomy_term_data';
  1097. $query =
  1098. db_select($table, 'td')
  1099. ->addTag('taxonomy_access')
  1100. ->addTag('taxonomy_access_grants')
  1101. ;
  1102. $query->join(
  1103. 'taxonomy_access_default', 'tadg',
  1104. 'tadg.vid = :vid',
  1105. array(':vid' => TAXONOMY_ACCESS_GLOBAL_DEFAULT)
  1106. );
  1107. $query->leftJoin(
  1108. 'taxonomy_access_default', 'tad',
  1109. 'tad.vid = td.vid AND tad.rid = tadg.rid'
  1110. );
  1111. if (!$default) {
  1112. $query->leftJoin(
  1113. 'taxonomy_access_term', 'ta',
  1114. 'ta.tid = td.tid AND ta.rid = tadg.rid'
  1115. );
  1116. }
  1117. // We add grant fields this way to reduce the risk of future vulnerabilities.
  1118. $grant_fields = array(
  1119. 'view' => 'grant_view',
  1120. 'update' => 'grant_update',
  1121. 'delete' => 'grant_delete',
  1122. 'create' => 'grant_create',
  1123. 'list' => 'grant_list',
  1124. );
  1125. foreach ($grant_fields as $name => $grant) {
  1126. if (in_array($name, $grants)) {
  1127. if ($default) {
  1128. $query->addExpression(
  1129. 'BIT_OR(COALESCE('
  1130. . 'tad.' . db_escape_table($grant) . ', '
  1131. . 'tadg.' . db_escape_table($grant)
  1132. . '))',
  1133. $grant
  1134. );
  1135. }
  1136. else {
  1137. $query->addExpression(
  1138. 'BIT_OR(COALESCE('
  1139. . 'ta.' . db_escape_table($grant) . ', '
  1140. . 'tad.' . db_escape_table($grant) . ', '
  1141. . 'tadg.' . db_escape_table($grant)
  1142. . '))',
  1143. $grant
  1144. );
  1145. }
  1146. }
  1147. }
  1148. return $query;
  1149. }
  1150. /**
  1151. * Calculates node access grants by role for the given node ID.
  1152. *
  1153. * @param $node_nid
  1154. * The node ID for which to calculate grants.
  1155. * @param $reset
  1156. * (optional) Whether to recalculate the cached values. Defaults to FALSE.
  1157. *
  1158. * @return
  1159. * Array formatted for hook_node_access_records().
  1160. *
  1161. * @ingroup tac_node_access
  1162. */
  1163. function _taxonomy_access_node_access_records($node_nid, $reset = FALSE) {
  1164. // Build the base node grant query.
  1165. $query = _taxonomy_access_grant_query(array('view', 'update', 'delete'));
  1166. // Select grants for this node only and group by role.
  1167. $query->join(
  1168. 'taxonomy_index', 'ti',
  1169. 'td.tid = ti.tid'
  1170. );
  1171. $query
  1172. ->fields('tadg', array('rid'))
  1173. ->condition('ti.nid', $node_nid)
  1174. ->groupBy('tadg.rid')
  1175. ->addTag('taxonomy_access_node_access')
  1176. ->addTag('taxonomy_access_node')
  1177. ;
  1178. // Fetch and format all grant records for the node.
  1179. $grants = array();
  1180. $records = $query->execute()->fetchAll();
  1181. // The node grant query returns no rows if the node has no tags.
  1182. // In that scenario, use the global default.
  1183. if (sizeof($records) == 0) {
  1184. $records = taxonomy_access_global_defaults($reset);
  1185. }
  1186. foreach ($records as $record) {
  1187. $grants[] = _taxonomy_access_format_node_access_record($record);
  1188. }
  1189. return $grants;
  1190. }
  1191. /**
  1192. * Returns an array of global default grants for all roles.
  1193. *
  1194. * @param bool $reset
  1195. * (optional) Whether to recalculate the cached values. Defaults to FALSE.
  1196. *
  1197. * @return array
  1198. * An array of global defaults for each role.
  1199. */
  1200. function taxonomy_access_global_defaults($reset = FALSE) {
  1201. $global_grants = &drupal_static(__FUNCTION__, array());
  1202. if (empty($global_grants) || $reset) {
  1203. $global_grants =
  1204. db_query(
  1205. 'SELECT rid, grant_view, grant_update, grant_delete, grant_create,
  1206. grant_list
  1207. FROM {taxonomy_access_default}
  1208. WHERE vid = :vid',
  1209. array(':vid' => TAXONOMY_ACCESS_GLOBAL_DEFAULT))
  1210. ->fetchAllAssoc('rid');
  1211. }
  1212. return $global_grants;
  1213. }
  1214. /**
  1215. * Formats a row for hook_node_access_records.
  1216. *
  1217. * @param stdClass $record
  1218. * The term record object from a TAC query to format.
  1219. *
  1220. * @return array
  1221. * An array formatted for hook_node_access_records().
  1222. *
  1223. * @todo
  1224. * Make priority configurable?
  1225. */
  1226. function _taxonomy_access_format_node_access_record(stdClass $record) {
  1227. // TAXONOMY_ACCESS_NODE_IGNORE => 0, TAXONOMY_ACCESS_NODE_ALLOW => 1,
  1228. // TAXONOMY_ACCESS_NODE_DENY => 2 ('10' in binary).
  1229. // Only a value of 1 is considered an 'Allow';
  1230. // with an 'Allow' and no 'Deny', the value from the BIT_OR will be 1.
  1231. // If a 'Deny' is present, the value will then be 3 ('11' in binary).
  1232. return array(
  1233. 'realm' => 'taxonomy_access_role',
  1234. 'gid' => $record->rid,
  1235. 'grant_view' => ($record->grant_view == 1) ? 1 : 0,
  1236. 'grant_update' => ($record->grant_update == 1) ? 1 : 0,
  1237. 'grant_delete' => ($record->grant_delete == 1) ? 1 : 0,
  1238. 'priority' => 0,
  1239. );
  1240. }
  1241. /**
  1242. * End of "defgroup tac_node_access".
  1243. * @}
  1244. */
  1245. /**
  1246. * @defgroup tac_list Taxonomy Access Control: View tag (list) permission
  1247. * @{
  1248. * Alter queries to control the display of taxonomy terms on nodes and listings.
  1249. */
  1250. /**
  1251. * Flag to disable list grant filtering (e.g., on node edit forms).
  1252. *
  1253. * @param bool $set_flag
  1254. * (optional) When passed, sets the the flag. Pass either TRUE or FALSE.
  1255. * Defaults to NULL.
  1256. */
  1257. function _taxonomy_access_list_state($set_flag = NULL) {
  1258. static $flag = TRUE;
  1259. // If no flag was passed, return the current state of the flag.
  1260. if (is_null($set_flag)) {
  1261. return $flag;
  1262. }
  1263. // If we were passed anything but null, set the flag.
  1264. $flag = $set_flag ? TRUE : FALSE;
  1265. }
  1266. /**
  1267. * Wrapper for taxonomy_access_list_state() to enable list grant filtering.
  1268. *
  1269. * @see _taxonomy_access_list_state()
  1270. */
  1271. function taxonomy_access_enable_list() {
  1272. _taxonomy_access_list_state(TRUE);
  1273. }
  1274. /**
  1275. * Wrapper for taxonomy_access_list_state() to disable list grant filtering.
  1276. *
  1277. * @see _taxonomy_access_list_state()
  1278. */
  1279. function taxonomy_access_disable_list() {
  1280. _taxonomy_access_list_state(FALSE);
  1281. }
  1282. /**
  1283. * Wrapper for taxonomy_access_list_state() to check list grant filtering.
  1284. *
  1285. * @see _taxonomy_access_list_state()
  1286. */
  1287. function taxonomy_access_list_enabled() {
  1288. return _taxonomy_access_list_state();
  1289. }
  1290. /**
  1291. * Retrieve terms that the current user may list.
  1292. *
  1293. * @return array|true
  1294. * An array of term IDs, or TRUE if the user may list all terms.
  1295. *
  1296. * @see _taxonomy_access_user_term_grants()
  1297. */
  1298. function taxonomy_access_user_list_terms() {
  1299. // Cache the terms the current user can list.
  1300. static $terms = NULL;
  1301. if (is_null($terms)) {
  1302. $terms = _taxonomy_access_user_term_grants(FALSE);
  1303. }
  1304. return $terms;
  1305. }
  1306. /**
  1307. * Retrieve terms that the current user may create or list.
  1308. *
  1309. * @param bool $create
  1310. * (optional) Whether to fetch grants for create (TRUE) or list (FALSE).
  1311. * Defaults to FALSE.
  1312. * @param array $vids
  1313. * (optional) An array of vids to limit the query. Defaults to array().
  1314. * @param object|null $account
  1315. * (optional) The account for which to retrieve grants. If no account is
  1316. * passed, the current user will be used. Defaults to NULL.
  1317. *
  1318. * @return array|true
  1319. * An array of term IDs, or TRUE if the user has the grant for all terms.
  1320. */
  1321. function _taxonomy_access_user_term_grants($create = FALSE, array $vids = array(), $account = NULL) {
  1322. $grant_type = $create ? 'create' : 'list';
  1323. $grant_field_name = 'grant_' . $grant_type;
  1324. // If no account was passed, default to current user.
  1325. if (is_null($account)) {
  1326. global $user;
  1327. $account = $user;
  1328. }
  1329. // If the user can administer taxonomy, return TRUE for a global grant.
  1330. if (user_access('administer taxonomy', $account)) {
  1331. return TRUE;
  1332. }
  1333. // Build a term grant query.
  1334. $query = _taxonomy_access_grant_query(array($grant_type));
  1335. // Select term grants for the user's roles.
  1336. $query
  1337. ->fields('td', array('tid'))
  1338. ->groupBy('td.tid')
  1339. ->condition('tadg.rid', array_keys($account->roles), 'IN')
  1340. ;
  1341. // Filter by the indicated vids, if any.
  1342. if (!empty($vids)) {
  1343. $query
  1344. ->fields('td', array('vid'))
  1345. ->condition('td.vid', $vids, 'IN')
  1346. ;
  1347. }
  1348. // Fetch term IDs.
  1349. $r = $query->execute()->fetchAll();
  1350. $tids = array();
  1351. // If there are results, initialize a flag to test whether the user
  1352. // has the grant for all terms.
  1353. $grants_for_all_terms = empty($r) ? FALSE : TRUE;
  1354. foreach ($r as $record) {
  1355. // If the user has the grant, add the term to the array.
  1356. if ($record->$grant_field_name) {
  1357. $tids[] = $record->tid;
  1358. }
  1359. // Otherwise, flag that the user does not have the grant for all terms.
  1360. else {
  1361. $grants_for_all_terms = FALSE;
  1362. }
  1363. }
  1364. // If the user has the grant for all terms, return TRUE for a global grant.
  1365. if ($grants_for_all_terms) {
  1366. return TRUE;
  1367. }
  1368. return $tids;
  1369. }
  1370. /**
  1371. * Field options callback to generate options unfiltered by list grants.
  1372. *
  1373. * @param object $field
  1374. * The field object.
  1375. *
  1376. * @return array
  1377. * Allowed terms from taxonomy_allowed_values().
  1378. *
  1379. * @see taxonomy_allowed_values()
  1380. */
  1381. function _taxonomy_access_term_options($field) {
  1382. // Preserve the original state of the list flag.
  1383. $flag_state = taxonomy_access_list_enabled();
  1384. // Enforce that list grants do not filter the options list.
  1385. taxonomy_access_disable_list();
  1386. // Use taxonomy.module to generate the list of options.
  1387. $options = taxonomy_allowed_values($field);
  1388. // Restore list flag to previous state.
  1389. if ($flag_state) {
  1390. taxonomy_access_enable_list();
  1391. }
  1392. return $options;
  1393. }
  1394. /**
  1395. * End of "defgroup tac_list".
  1396. * @}
  1397. */
  1398. /**
  1399. * Form element validation handler for taxonomy autocomplete fields.
  1400. *
  1401. * @see taxonomy_access_autocomplete()
  1402. * @see taxonomy_access_field_widget_taxonomy_autocomplete_form_alter()
  1403. */
  1404. function taxonomy_access_autocomplete_validate($element, &$form_state) {
  1405. // Enforce that list grants do not filter this or subsequent validation.
  1406. taxonomy_access_disable_list();
  1407. // Add create grant handling.
  1408. module_load_include('inc', 'taxonomy_access', 'taxonomy_access.create');
  1409. _taxonomy_access_autocomplete_validate($element, $form_state);
  1410. }
  1411. /**
  1412. * Form element validation handler for taxonomy options fields.
  1413. *
  1414. * @see taxonomy_access_field_widget_form_alter()
  1415. */
  1416. function taxonomy_access_options_validate($element, &$form_state) {
  1417. // Enforce that list grants do not filter this or subsequent validation.
  1418. taxonomy_access_disable_list();
  1419. // Add create grant handling.
  1420. module_load_include('inc', 'taxonomy_access', 'taxonomy_access.create');
  1421. _taxonomy_access_options_validate($element, $form_state);
  1422. }
  1423. /**
  1424. * Implements hook_help().
  1425. */
  1426. function taxonomy_access_help($path, $arg) {
  1427. switch ($path) {
  1428. case 'admin/help#taxonomy_access':
  1429. $message = '';
  1430. $message .= ''
  1431. . '<p>' . t('The Taxonomy Access Control module allows users to specify how each category can be used by various roles.') . '</p>'
  1432. . '<p>' . t('Permissions can be set differently for each user role. Be aware that setting Taxonomy Access permissions works <em>only within one user role</em>.') . '</p>'
  1433. . '<p>' . t('(For users with multiple user roles, see section <a href="#good-to-know">Good to know</a> below.)') . '</p><hr /><br />'
  1434. . "<h3>" . t("On this page") . "</h3>"
  1435. . "<ol>"
  1436. . '<li><a href="#grant">' . t("Grant types") . '</a></li>'
  1437. . '<li><a href="#perm">' . t("Permission options") . '</a></li>'
  1438. . '<li><a href="#defaults">' . t("Global and vocabulary defaults") . '</a></li>'
  1439. . '<li><a href="#good-to-know">' . t("Good to know") . '</a></li>'
  1440. . "</ol><hr /><br />"
  1441. . '<h3 id="grant">' . t("Grant types") . '</h3>'
  1442. . '<p>' . t('On the category permissions page for each role, administrators can configure five types of permission for each term: <em>View, Update, Delete, Add Tag</em> (formerly <em>Create</em>), and <em>View Tag</em>: (formerly <em>List</em>') . '</p>'
  1443. . _taxonomy_access_grant_help_table()
  1444. . '<p>' . t('<em>View</em>, <em>Update</em>, and <em>Delete</em> control the node access system. <em>View Tag</em> and <em>Add Tag</em> control the terms themselves. (Note: In previous versions of Taxonomy Access Control, there was no <em>View Tag</em> permission its functionality was controlled by the <em>View</em> permission.)') . '</p><hr /><br />'
  1445. . '<h3 id="perm">' . t("Permission options") . "</h3>"
  1446. . '<p>' . t('<strong><em>View</em>, <em>Update</em>, and <em>Delete</em> have three options for each term:</strong> <em>Allow</em> (<acronym title="Allow">A</acronym>), <em>Ignore</em> (<acronym title="Ignore">I</acronym>), and <em>Deny</em> (<acronym title="Deny">D</acronym>). Indicate which rights each role should have for each term. If a node is tagged with multiple terms:') . '</p>'
  1447. . "<ul>\n"
  1448. . "<li>"
  1449. . t('<em>Deny</em> (<acronym title="Deny">D</acronym>) overrides <em>Allow</em> (<acronym title="Allow">A</acronym>) within a role.')
  1450. . "</li>"
  1451. . "<li>"
  1452. . t('Both <em>Allow</em> (<acronym title="Allow">A</acronym>) and <em>Deny</em> (<acronym title="Deny">D</acronym>) override <em>Ignore</em> (<acronym title="Ignore">I</acronym>) within a role.')
  1453. . "</li>"
  1454. . "<li>"
  1455. . t('If a user has <strong>multiple roles</strong>, an <em>Allow</em> (<acronym title="Allow">A</acronym>) from one role <strong>will</strong> override a <em>Deny</em> (<acronym title="Deny">D</acronym>) in another. (For more information, see section <a href="#good-to-know">Good to know</a> below.)')
  1456. . "</li>"
  1457. . "</ul>\n\n"
  1458. . '<p>' . t('<strong><em>Add Tag</em> and <em>View Tag</em> have only two options for each term:</strong> <em>Yes</em> (selected) or <em>No</em> (deselected). Indicate what each role should be allowed to do with each term.') . '</p>'
  1459. . "<h4>" . t("Important notes") . "</h4>"
  1460. . "<ol>"
  1461. . "<li>"
  1462. . t('Custom roles <strong>will</strong> inherit permissions from the <em>authenticated user</em> role. Be sure to <a href="@url">configure
  1463. the authenticated user</a> properly.',
  1464. array("@url" => url(
  1465. TAXONOMY_ACCESS_CONFIG
  1466. . '/role/'
  1467. . DRUPAL_AUTHENTICATED_RID
  1468. . 'edit')))
  1469. . "</li>\n"
  1470. . '<li>'
  1471. . "<p>" . t('The <em>Deny</em> directives are processed after the <em>Allow</em> directives. (<strong><em>Deny</em> overrides <em>Allow</em></strong>.)</em> So, if a multicategory node is in Categories "A" and "B" and a user has <em>Allow</em> permissions for <em>View</em> in Category "A" and <em>Deny</em> permissions for <em>View</em> in Category "B", then the user will NOT be permitted to <em>View</em> the node.') . '</p>'
  1472. . '<p>' . t('<em>Access is denied by default.</em> So, if a multicategory node is in Categories "C" and "D" and a user has <em>Ignore</em> permissions for <em>View</em> in both Category "C" and "D", then the user will <strong>not</strong> be permitted to view the node.') . '</p>'
  1473. . '<p>' . t('(If you are familiar with Apache mod_access, this permission system works similar to directive: <em>ORDER ALLOW, DENY</em>)') . '</p>'
  1474. . "</li>"
  1475. . "</ol>"
  1476. . "<hr /><br />"
  1477. . '<h3 id="defaults">' . t("Global and vocabulary defaults") . "</h3>"
  1478. . '<p>' . t('This option, just underneath the vocabulary title, <em>sets the permission that will automatically be given</em> to the role, <em>for any new terms</em> that are added within the vocabulary. This includes terms that are added via free tagging.') . '</p><hr /><br />'
  1479. . '<h3 id="good-to-know">' . t('Good to know') . '</h3>'
  1480. . '<ol>'
  1481. . '<li>'
  1482. . '<p>' . t('<strong>Users with multiple user roles:</strong> Allow/Ignore/Deny options are interpreted <em>only within one user role</em>. When a user belongs to multiple user roles, then <strong>the user gets access if <em>any</em> of his/her user roles have the access granted.</strong>') . '</p>'
  1483. . '<p>' . t('In this case, permissions for the given user are calculated so that the <em>permissions of ALL of his user roles are "OR-ed" together</em>, which means that <em>Allow</em> in one role will take precedence over <em>Deny</em> in the other. This is different from how node access permissions (for multi-category nodes) are handled <em>within one user role</em>, as noted above.') . '</p>'
  1484. . '</li>'
  1485. . '<li>'
  1486. . '<p>' . t('<strong>Input formats:</strong> <em>Node editing/deleting is blocked</em>, even when the user has <em>Update</em> or <em>Delete</em> permission to the node, <em>when the user is not allowed to use a filter format</em> that the node was saved at.') . '</p>'
  1487. . '</li>'
  1488. . '</ol>'
  1489. . '<hr /><br />'
  1490. ;
  1491. return $message;
  1492. break;
  1493. }
  1494. }
  1495. /**
  1496. * Assembles a table explaining each grant type for use in help documentation.
  1497. *
  1498. * @return string
  1499. * Themed table.
  1500. *
  1501. * @todo
  1502. * We moved this here for drush. Find a smarter way to include it on demand?
  1503. */
  1504. function _taxonomy_access_grant_help_table() {
  1505. $header = array();
  1506. $rows = array();
  1507. $rows[] = array(
  1508. array('header' => TRUE, 'data' => t("View")),
  1509. "<p>"
  1510. . t('Grants this role the ability to view nodes with the term. (Users must also have this permission to see <em class="perm">nodes</em> with the term listed in Views.)')
  1511. . "</p>"
  1512. . "<p>"
  1513. . t('The role must <strong>have</strong> <em class="perm">access content</em> permission on the <a href="@path">permissions administration form</a>.',
  1514. array('@path' => url('admin/people/permissions', array('fragment' => 'module-node')))),
  1515. );
  1516. $rows[] = array(
  1517. array('header' => TRUE, 'data' => t("Update") . ", " . t("Delete")),
  1518. "<p>"
  1519. . t("Grants this role the ability to edit or delete nodes with the term, respectively.")
  1520. . "</p>"
  1521. . "<p>"
  1522. . t('The role must <strong>not</strong> have <em class="perm">edit any [type] content</em> or <em class="perm">delete any [type] content</em> permission on the <a href="@path">permissions administration form</a> if you wish to control them here.',
  1523. array('@path' => url('admin/people/permissions', array('fragment' => 'module-node'))))
  1524. . "</p>",
  1525. );
  1526. $rows[] = array(
  1527. array('header' => TRUE, 'data' => t("Add Tag")),
  1528. "<p>"
  1529. . t("Grants this role the ability to add the term to a node when creating or updating it.")
  1530. . "</p>"
  1531. . "<p>"
  1532. . t('(Formerly <em>Create</em>). This does <strong>not</strong> give the role the ability to create nodes by itself; the role must <strong>have</strong> <em class="perm">create [type] content</em> permission on the <a href="@path">permissions administration form</a> in order to create new nodes.',
  1533. array('@path' => url('admin/people/permissions', array('fragment' => 'module-node'))))
  1534. . "</p>",
  1535. );
  1536. $rows[] = array(
  1537. array('header' => TRUE, 'data' => t("View Tag")),
  1538. "<p>"
  1539. . t("(Formerly <em>List</em>.) Whether this role can see the term listed on node pages and in lists, and whether the user can view the %taxonomy-term-page page for the term.",
  1540. array(
  1541. '%taxonomy-term-page' => "taxonomy/term/x"
  1542. ))
  1543. . "</p>"
  1544. . "<p>" . t("This does <strong>not</strong> control whether the role can see the <em>nodes</em> listed in Views, only the <em>term</em>.") . "</p>",
  1545. );
  1546. return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('grant_help'))));
  1547. }
  1548. /**
  1549. * Implements hook_disable().
  1550. *
  1551. * Removes all options_list callbacks during disabling of the module which were
  1552. * set in taxonomy_access_field_info_alter().
  1553. */
  1554. function taxonomy_access_disable() {
  1555. foreach (field_read_fields() as $field_name => $field) {
  1556. if ($field['type'] == 'taxonomy_term_reference') {
  1557. if (!empty($field['settings']['options_list_callback']) && $field['settings']['options_list_callback'] == '_taxonomy_access_term_options') {
  1558. $field['settings']['options_list_callback'] = '';
  1559. field_update_field($field);
  1560. }
  1561. }
  1562. }
  1563. }