node_access_example.module

  1. examples
    1. 6 node_access_example/node_access_example.module
    2. 7 node_access_example/node_access_example.module
    3. 8 node_access_example/node_access_example.module
  2. drupal
    1. 4.6 developer/examples/node_access_example.module
    2. 4.7 developer/examples/node_access_example.module
    3. 5 developer/examples/node_access_example.module

Module file illustrating API-based node access.

Functions & methods

NameDescription
node_access_example_form_alterImplements hook_form_alter().
node_access_example_menuImplements hook_menu().
node_access_example_node_accessImplements hook_node_access().
node_access_example_node_access_recordsImplements hook_node_access_records().
node_access_example_node_deleteImplements hook_node_delete().
node_access_example_node_grantsImplements hook_node_grants().
node_access_example_node_insertImplements hook_node_insert().
node_access_example_node_loadImplements hook_node_load().
node_access_example_node_updateImplements hook_node_update().
node_access_example_permissionImplements hook_permission().
node_access_example_private_node_listingOur hook_menu() page callback function.

Constants

NameDescription
NODE_ACCESS_EXAMPLE_GRANT_ALLHere we define a constant for our node access grant ID, for the node_access_example_view and node_access_example_edit realms. This ID could be any integer, but here we choose 23, because it is this author's favorite number.

File

node_access_example/node_access_example.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * Module file illustrating API-based node access.
  5. */
  6. /**
  7. * @defgroup node_access_example Example: Node Access
  8. * @ingroup examples
  9. * @{
  10. * Demonstrates node access.
  11. *
  12. * This is an example demonstrating how to grant or deny access to nodes
  13. * using the Drupal core API node access system.
  14. *
  15. * This module will add a 'private' flag for each node, which the node's
  16. * author can manage. Nodes marked private can only be viewed, edited,
  17. * or deleted by the author. However, not everything is as private as it
  18. * seems on the internet, and so we need to implement some ways to allow
  19. * other users to manage this 'private' content.
  20. *
  21. * We will use the node grant system to specify which users
  22. * are allowed to view, edit, or delete 'private' content. We will also
  23. * allow a user named 'foobar' to have edit privileges on private content
  24. * as well.
  25. *
  26. * In addition, we will provide a page which will show some minimal
  27. * instructions, and statistics on private nodes on the site.
  28. *
  29. * We use NodeAPI hooks to put a single marker on a node, called
  30. * 'private'. The marker is implemented by a database table which has one
  31. * row per node simply indicating that the node is private. If the "private"
  32. * marker is set, users other than the owner and privileged users are denied
  33. * access.
  34. *
  35. * Standard permissions are defined which allow users with
  36. * 'access any private content' or 'edit any private content' to override
  37. * the 'private' node access restrictions.
  38. *
  39. * A separate access realm grants privileges to each node's author, so that
  40. * they can always view, edit, and delete their own private nodes.
  41. *
  42. * The only page provided by this module gives a rundown of how many nodes
  43. * are marked private, and how many of those are accessible to the current
  44. * user. This demonstrates the use of the 'node_access' tag in node queries,
  45. * preventing disclosure of information which should not be shown to users
  46. * who don't have the proper permissions.
  47. *
  48. * Most relevant functions:
  49. * - node_access_example_permission()
  50. * - node_access_example_node_access()
  51. * - node_access_example_node_access_records()
  52. * - node_access_example_node_grants()
  53. *
  54. * Drupal's node access system has three layers.
  55. * - Overall override permissions. User 1 and any user with 'bypass node
  56. * access' permission are automatically granted access.
  57. * - hook_node_access() gives each module the opportunity to approve or deny
  58. * access. Any module that returns NODE_ACCESS_DENY from hook_node_access()
  59. * will result in denial of access. If no module denies access and one or
  60. * more modules allow access, then access is granted. hook_node_access() was
  61. * introduced in Drupal 7.
  62. * - If no resolution has yet been reached, then the node_access table is used
  63. * along with hook_node_grants(). (Drupal updates the node_access table when
  64. * nodes are saved, by calling hook_node_access_records().)
  65. *
  66. * Note that the hook_node_grants()/hook_node_access_records() layer is a
  67. * first-grant-wins system, which means a module using it can't deny access to
  68. * a node. Contributed modules have been developed to overcome this
  69. * shortcoming, with their own APIs, such as
  70. * @link http://drupal.org/project/acl ACL. @endlink ACL, in fact, has emerged
  71. * as the more-or-less standard solution for fine-grained access control, and
  72. * you really should be reading up on it. Many modules use it, and if your
  73. * module implements another node access system, there could be chaos.
  74. *
  75. * A list of the many node access modules (and differing APIs) is here:
  76. * @link http://drupal.org/node/270000 Overview of Node Access Modules. @endlink
  77. * Note that this is Drupal 6 documentation, linked here so the reader can
  78. * understand the historical reasons for using contributed node access modules
  79. * such as ACL.
  80. *
  81. * @see node_access()
  82. * @see hook_node_access()
  83. * @see hook_node_grants()
  84. * @see hook_node_access_records()
  85. */
  86. /**
  87. * Implements hook_menu().
  88. *
  89. * This path provides a page, with some instructions for the user, and some
  90. * statistics about node access changes implemented by this module.
  91. *
  92. * @see hook_menu()
  93. */
  94. function node_access_example_menu() {
  95. $items['examples/node_access'] = array(
  96. 'title' => 'Node Access Example',
  97. 'page callback' => 'node_access_example_private_node_listing',
  98. 'access callback' => TRUE,
  99. );
  100. return $items;
  101. }
  102. /**
  103. * Our hook_menu() page callback function.
  104. *
  105. * Information for the user about what nodes are marked private on the system
  106. * and which of those the user has access to.
  107. *
  108. * The queries showing what is accessible to the current user demonstrate the
  109. * use of the 'node_access' tag to make sure that we don't show inappropriate
  110. * information to unprivileged users.
  111. *
  112. * @return Page content.
  113. *
  114. * @see page_example
  115. */
  116. function node_access_example_private_node_listing() {
  117. $content = '<div>' . t('This example shows how a module can use the Drupal node access system to allow access to specific nodes. You will need to look at the code and then experiment with it by creating nodes, marking them private, and accessing them as various users.') . '</div>';
  118. // Find out how many nodes are marked private.
  119. $query = db_select('node', 'n');
  120. $query->addExpression('COUNT(n.nid)', 'private_count');
  121. $query->join('node_access_example', 'nae', 'nae.nid = n.nid');
  122. $num_private = $query
  123. ->condition('nae.private', 1)->execute()->fetchField();
  124. // Find out how many nodes owned by this user are marked private.
  125. $query = db_select('node', 'n');
  126. $query->addExpression('COUNT(n.nid)', 'private_count');
  127. $query->join('node_access_example', 'nae', 'nae.nid = n.nid');
  128. $num_personal = $query
  129. ->condition('n.uid', $GLOBALS['user']->uid)
  130. ->condition('nae.private', 1)
  131. ->execute()->fetchfield();
  132. $content .= '<div>' . t('There are currently @num private nodes in the system @num_personal are yours.', array('@num' => $num_private, '@num_personal' => $num_personal)) . '</div>';
  133. // Use a 'node_access' tag with a query to find out how many this user has
  134. // access to. This will be the standard way to make lists while respecting
  135. // node access restrictions.
  136. $query = db_select('node', 'n');
  137. $query->addExpression('COUNT(n.nid)', 'private_count');
  138. $query->addTag('node_access');
  139. $query->join('node_access_example', 'nae', 'nae.nid = n.nid');
  140. $num_private_accessible = $query->condition('nae.private', 1)->execute()->fetchField();
  141. $content .= '<div>' . t('You have access to @num private nodes.', array('@num' => $num_private_accessible)) . '</div>';
  142. // Use the key 'node_access' tag to get the key data from the nodes this
  143. // has access to.
  144. $query = db_select('node', 'n', array('fetch' => PDO::FETCH_ASSOC));
  145. $query->addTag('node_access');
  146. $query->join('node_access_example', 'nae', 'nae.nid = n.nid');
  147. $query->join('users', 'u', 'u.uid = n.uid');
  148. $result = $query->fields('n', array('nid', 'title', 'uid'))
  149. ->fields('u', array('name'))
  150. ->condition('nae.private', 1)->execute();
  151. $rows = array();
  152. foreach ($result as $node) {
  153. $node['nid'] = l($node['nid'], 'node/' . $node['nid']);
  154. $rows[] = array('data' => $node, 'class' => array('accessible'));
  155. }
  156. $content .= '<div>' . t('Accessible rows:') . theme('table', array('header' => array('nid', 'title', 'uid', 'username'), 'rows' => $rows)) . '</div>';
  157. return array('#markup' => $content);
  158. }
  159. /**
  160. * Implements hook_permission().
  161. *
  162. * We create two permissions, which we can use as a base for our grant/deny
  163. * decision:
  164. *
  165. * - 'access any private content' allows global access to content marked
  166. * private by other users.
  167. * - 'edit any private content' allows global edit
  168. * privileges, basically overriding the node access system.
  169. *
  170. * Note that the 'edit any * content' and 'delete any * content' permissions
  171. * will allow edit or delete permissions to the holder, regardless of what
  172. * this module does.
  173. *
  174. * @see hook_permissions()
  175. */
  176. function node_access_example_permission() {
  177. return array(
  178. 'access any private content' => array(
  179. 'title' => t('Access any private content'),
  180. 'description' => t('May view posts of other users even though they are marked private.'),
  181. ),
  182. 'edit any private content' => array(
  183. 'title' => t('Edit any private content'),
  184. 'description' => t('May edit posts of other users even though they are marked private.'),
  185. ),
  186. );
  187. }
  188. /**
  189. * Implements hook_node_access().
  190. *
  191. * Allows view and edit access to private nodes, when the account requesting
  192. * access has the username 'foobar'.
  193. *
  194. * hook_node_access() was introduced in Drupal 7. We use it here to demonstrate
  195. * allowing certain privileges to an arbitrary user.
  196. *
  197. * @see hook_node_access()
  198. */
  199. function node_access_example_node_access($node, $op, $account) {
  200. // If $node is a string, the node has not yet been created. We don't care
  201. // about that case.
  202. if (is_string($node)) {
  203. return NODE_ACCESS_IGNORE;
  204. }
  205. if (($op == 'view' || $op == 'update') && (!empty($account->name) && $account->name == 'foobar') && !empty($node->private)) {
  206. drupal_set_message(t('Access to node @nid allowed because requester name (@name) is specifically allowed', array('@name' => $node->name, '@uid' => $account->uid)));
  207. return NODE_ACCESS_ALLOW;
  208. }
  209. return NODE_ACCESS_IGNORE;
  210. }
  211. /**
  212. * Here we define a constant for our node access grant ID, for the
  213. * node_access_example_view and node_access_example_edit realms. This ID could
  214. * be any integer, but here we choose 23, because it is this author's favorite
  215. * number.
  216. */
  217. define('NODE_ACCESS_EXAMPLE_GRANT_ALL', 23);
  218. /**
  219. * Implements hook_node_grants().
  220. *
  221. * Tell the node access system what grant IDs the user belongs to for each
  222. * realm, based on the operation being performed.
  223. *
  224. * When the user tries to perform an operation on the node, Drupal calls
  225. * hook_node_grants() to determine grant ID and realm for the user. Drupal
  226. * looks up the grant ID and realm for the node, and compares them to the
  227. * grant ID and realm provided here. If grant ID and realm match for both
  228. * user and node, then the operation is allowed.
  229. *
  230. * Grant ID and realm are both determined per node, by your module in
  231. * hook_node_access_records().
  232. *
  233. * In our example, we've created three access realms: One for authorship, and
  234. * two that track with the permission system.
  235. *
  236. * We always add node_access_example_author to the list of grants, with a grant
  237. * ID equal to their user ID. We do this because in our model, authorship
  238. * always gives you permission to edit or delete your nodes, even if they're
  239. * marked private.
  240. *
  241. * Then we compare the user's permissions to the operation to determine whether
  242. * the user falls into the other two realms: node_access_example_view, and/or
  243. * node_access_example_edit. If the user has the 'access any private content'
  244. * permission we defined in hook_permission(), they're declared as belonging to
  245. * the node_access_example_realm. Similarly, if they have the 'edit any private
  246. * content' permission, we add the node_access_example_edit realm to the list
  247. * of grants they have.
  248. *
  249. * @see node_access_example_permission()
  250. * @see node_access_example_node_access_records()
  251. */
  252. function node_access_example_node_grants($account, $op) {
  253. // First grant a grant to the author for own content.
  254. $grants['node_access_example_author'] = array($account->uid);
  255. // Then, if "access any private content" is allowed to the account,
  256. // grant view, update, or delete as necessary.
  257. if ($op == 'view' && user_access('access any private content', $account)) {
  258. $grants['node_access_example_view'] = array(NODE_ACCESS_EXAMPLE_GRANT_ALL);
  259. }
  260. if (($op == 'update' || $op == 'delete') && user_access('edit any private content', $account)) {
  261. $grants['node_access_example_edit'] = array(NODE_ACCESS_EXAMPLE_GRANT_ALL);
  262. }
  263. return $grants;
  264. }
  265. /**
  266. * Implements hook_node_access_records().
  267. *
  268. * All node access modules must implement this hook. If the module is
  269. * interested in the privacy of the node passed in, return a list
  270. * of node access values for each grant ID we offer.
  271. *
  272. * In this example, for each node which is marked 'private,' we define
  273. * three realms:
  274. *
  275. * The first and second are realms are 'node_access_example_view' and
  276. * 'node_access_example_edit,' which have a single grant ID, 1. The
  277. * user is either a member of these realms or not, depending upon the
  278. * operation and the access permission set.
  279. *
  280. * The third is node_access_example_author. It gives the node
  281. * author special privileges. node_access_example_author has one grant ID for
  282. * every UID, and each user is automatically a member of the group where
  283. * GID == UID. This has the effect of giving each user their own grant ID
  284. * for nodes they authored, within this realm.
  285. *
  286. * Drupal calls this hook when a node is saved, or when access permissions
  287. * change in order to rebuild the node access database table(s).
  288. *
  289. * The array you return will define the realm and the grant ID for the
  290. * given node. This is stored in the {node_access} table for subsequent
  291. * comparison against the user's realm and grant IDs, which you'll
  292. * supply in hook_node_grants().
  293. *
  294. * Realm names and grant IDs are arbitrary. Official drupal naming
  295. * conventions do not cover access realms, but since all realms are
  296. * stored in the same database table, it's probably a good idea to
  297. * use descriptive names which follow the module name, such as
  298. * 'mymodule_realmname'.
  299. *
  300. * @see node_access_example_node_grants()
  301. */
  302. function node_access_example_node_access_records($node) {
  303. // We only care about the node if it's been marked private. If not, it is
  304. // treated just like any other node and we completely ignore it.
  305. if (!empty($node->private)) {
  306. $grants = array();
  307. $grants[] = array(
  308. 'realm' => 'node_access_example_view',
  309. 'gid' => NODE_ACCESS_EXAMPLE_GRANT_ALL,
  310. 'grant_view' => 1,
  311. 'grant_update' => 0,
  312. 'grant_delete' => 0,
  313. 'priority' => 0,
  314. );
  315. $grants[] = array(
  316. 'realm' => 'node_access_example_edit',
  317. 'gid' => NODE_ACCESS_EXAMPLE_GRANT_ALL,
  318. 'grant_view' => 1,
  319. 'grant_update' => 1,
  320. 'grant_delete' => 1,
  321. 'priority' => 0,
  322. );
  323. // For the node_access_example_author realm, the grant ID (gid) is
  324. // equivalent to the node author's user ID (UID).
  325. $grants[] = array(
  326. 'realm' => 'node_access_example_author',
  327. 'gid' => $node->uid,
  328. 'grant_view' => 1,
  329. 'grant_update' => 1,
  330. 'grant_delete' => 1,
  331. 'priority' => 0,
  332. );
  333. return $grants;
  334. }
  335. // Return nothing if the node has not been marked private.
  336. }
  337. /**
  338. * Implements hook_form_alter().
  339. *
  340. * This module adds a simple checkbox to the node form labeled private. If the
  341. * checkbox is checked, only the node author and users with
  342. * 'access any private content' privileges may see it.
  343. */
  344. function node_access_example_form_alter(&$form, $form_state) {
  345. if (!empty($form['#node_edit_form'])) {
  346. $form['node_access_example'] = array(
  347. '#type' => 'fieldset',
  348. '#title' => t('Node Access Example'),
  349. '#collapsible' => TRUE,
  350. '#collapsed' => FALSE,
  351. '#weight' => 8,
  352. );
  353. $form['node_access_example']['private'] = array(
  354. '#type' => 'checkbox',
  355. '#title' => t('Private'),
  356. '#description' => t('Check here if this content should be set private and only shown to privileged users.'),
  357. '#default_value' => isset($form['#node']->private) ? $form['#node']->private : FALSE,
  358. );
  359. }
  360. }
  361. /**
  362. * Implements hook_node_load().
  363. *
  364. * Gather and add the private setting for the nodes Drupal is loading.
  365. * @see nodeapi_example.module
  366. */
  367. function node_access_example_node_load($nodes, $types) {
  368. $result = db_query('SELECT nid, private FROM {node_access_example} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)));
  369. foreach ($result as $record) {
  370. $nodes[$record->nid]->private = $record->private;
  371. }
  372. }
  373. /**
  374. * Implements hook_node_delete().
  375. *
  376. * Delete the node_access_example record when the node is deleted.
  377. * @see nodeapi_example.module
  378. */
  379. function node_access_example_node_delete($node) {
  380. db_delete('node_access_example')->condition('nid', $node->nid)->execute();
  381. }
  382. /**
  383. * Implements hook_node_insert().
  384. *
  385. * Insert a new access record when a node is created.
  386. * @see nodeapi_example.module
  387. */
  388. function node_access_example_node_insert($node) {
  389. if (isset($node->private)) {
  390. db_insert('node_access_example')->fields(array('nid' => $node->nid, 'private' => (int)$node->private))->execute();
  391. }
  392. drupal_set_message(t('New node @nid was created and private=@private', array('@nid' => $node->nid, '@private' => !empty($node->private) ? 1 : 0)));
  393. }
  394. /**
  395. * Implements hook_node_update().
  396. *
  397. * If the record in the node_access_example table already exists, we must
  398. * update it. If it doesn't exist, we create it.
  399. * @see nodeapi_example.module
  400. */
  401. function node_access_example_node_update($node) {
  402. // Find out if there is already a node_access_example record.
  403. $exists = db_query('SELECT nid FROM {node_access_example} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField();
  404. // If there is already a record, update it with the new private value.
  405. if ($exists) {
  406. $num_updated = db_update('node_access_example')
  407. ->fields(array(
  408. 'nid' => $node->nid,
  409. 'private' => !empty($node->private) ? 1 : 0,
  410. ))
  411. ->condition('nid', $node->nid)
  412. ->execute();
  413. drupal_set_message(t("Updated node @nid to set private=@private (@num nodes actually updated)", array('@private' => $node->private, '@num' => $num_updated, '@nid' => $node->nid)));
  414. }
  415. // Otherwise, create a new record.
  416. else {
  417. node_access_example_node_insert($node);
  418. drupal_set_message(t('Inserted new node_access nid=@nid, private=@private', array('@nid' => $node->nid, '@private' => $node->private)));
  419. }
  420. }
  421. /**
  422. * @} End of "defgroup node_access_example".
  423. */
Login or register to post comments