node_access_example.module 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  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 string
  113. * Page content.
  114. *
  115. * @see page_example
  116. */
  117. function node_access_example_private_node_listing() {
  118. $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>';
  119. // Find out how many nodes are marked private.
  120. $query = db_select('node', 'n');
  121. $query->addExpression('COUNT(n.nid)', 'private_count');
  122. $query->join('node_access_example', 'nae', 'nae.nid = n.nid');
  123. $num_private = $query
  124. ->condition('nae.private', 1)->execute()->fetchField();
  125. // Find out how many nodes owned by this user are marked private.
  126. $query = db_select('node', 'n');
  127. $query->addExpression('COUNT(n.nid)', 'private_count');
  128. $query->join('node_access_example', 'nae', 'nae.nid = n.nid');
  129. $num_personal = $query
  130. ->condition('n.uid', $GLOBALS['user']->uid)
  131. ->condition('nae.private', 1)
  132. ->execute()->fetchfield();
  133. $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>';
  134. // Use a 'node_access' tag with a query to find out how many this user has
  135. // access to. This will be the standard way to make lists while respecting
  136. // node access restrictions.
  137. $query = db_select('node', 'n');
  138. $query->addExpression('COUNT(n.nid)', 'private_count');
  139. $query->addTag('node_access');
  140. $query->join('node_access_example', 'nae', 'nae.nid = n.nid');
  141. $num_private_accessible = $query->condition('nae.private', 1)->execute()->fetchField();
  142. $content .= '<div>' . t('You have access to @num private nodes.', array('@num' => $num_private_accessible)) . '</div>';
  143. // Use the key 'node_access' tag to get the key data from the nodes this
  144. // has access to.
  145. $query = db_select('node', 'n', array('fetch' => PDO::FETCH_ASSOC));
  146. $query->addTag('node_access');
  147. $query->join('node_access_example', 'nae', 'nae.nid = n.nid');
  148. $query->join('users', 'u', 'u.uid = n.uid');
  149. $result = $query->fields('n', array('nid', 'title', 'uid'))
  150. ->fields('u', array('name'))
  151. ->condition('nae.private', 1)->execute();
  152. $rows = array();
  153. foreach ($result as $node) {
  154. $node['nid'] = l($node['nid'], 'node/' . $node['nid']);
  155. $rows[] = array('data' => $node, 'class' => array('accessible'));
  156. }
  157. $content .= '<div>' . t('Accessible rows:') .
  158. theme('table',
  159. array(
  160. 'header' => array('nid', 'title', 'uid', 'username'),
  161. 'rows' => $rows,
  162. )
  163. ) .
  164. '</div>';
  165. return array('#markup' => $content);
  166. }
  167. /**
  168. * Implements hook_permission().
  169. *
  170. * We create two permissions, which we can use as a base for our grant/deny
  171. * decision:
  172. *
  173. * - 'access any private content' allows global access to content marked
  174. * private by other users.
  175. * - 'edit any private content' allows global edit
  176. * privileges, basically overriding the node access system.
  177. *
  178. * Note that the 'edit any * content' and 'delete any * content' permissions
  179. * will allow edit or delete permissions to the holder, regardless of what
  180. * this module does.
  181. *
  182. * @see hook_permissions()
  183. */
  184. function node_access_example_permission() {
  185. return array(
  186. 'access any private content' => array(
  187. 'title' => t('Access any private content'),
  188. 'description' => t('May view posts of other users even though they are marked private.'),
  189. ),
  190. 'edit any private content' => array(
  191. 'title' => t('Edit any private content'),
  192. 'description' => t('May edit posts of other users even though they are marked private.'),
  193. ),
  194. );
  195. }
  196. /**
  197. * Implements hook_node_access().
  198. *
  199. * Allows view and edit access to private nodes, when the account requesting
  200. * access has the username 'foobar'.
  201. *
  202. * hook_node_access() was introduced in Drupal 7. We use it here to demonstrate
  203. * allowing certain privileges to an arbitrary user.
  204. *
  205. * @see hook_node_access()
  206. */
  207. function node_access_example_node_access($node, $op, $account) {
  208. // If $node is a string, the node has not yet been created. We don't care
  209. // about that case.
  210. if (is_string($node)) {
  211. return NODE_ACCESS_IGNORE;
  212. }
  213. if (($op == 'view' || $op == 'update') && (!empty($account->name) && $account->name == 'foobar') && !empty($node->private)) {
  214. drupal_set_message(t('Access to node @nid allowed because requester name (@name) is specifically allowed', array('@name' => $node->name, '@uid' => $account->uid)));
  215. return NODE_ACCESS_ALLOW;
  216. }
  217. return NODE_ACCESS_IGNORE;
  218. }
  219. /**
  220. * Here we define a constant for our node access grant ID, for the
  221. * node_access_example_view and node_access_example_edit realms. This ID could
  222. * be any integer, but here we choose 23, because it is this author's favorite
  223. * number.
  224. */
  225. define('NODE_ACCESS_EXAMPLE_GRANT_ALL', 23);
  226. /**
  227. * Implements hook_node_grants().
  228. *
  229. * Tell the node access system what grant IDs the user belongs to for each
  230. * realm, based on the operation being performed.
  231. *
  232. * When the user tries to perform an operation on the node, Drupal calls
  233. * hook_node_grants() to determine grant ID and realm for the user. Drupal
  234. * looks up the grant ID and realm for the node, and compares them to the
  235. * grant ID and realm provided here. If grant ID and realm match for both
  236. * user and node, then the operation is allowed.
  237. *
  238. * Grant ID and realm are both determined per node, by your module in
  239. * hook_node_access_records().
  240. *
  241. * In our example, we've created three access realms: One for authorship, and
  242. * two that track with the permission system.
  243. *
  244. * We always add node_access_example_author to the list of grants, with a grant
  245. * ID equal to their user ID. We do this because in our model, authorship
  246. * always gives you permission to edit or delete your nodes, even if they're
  247. * marked private.
  248. *
  249. * Then we compare the user's permissions to the operation to determine whether
  250. * the user falls into the other two realms: node_access_example_view, and/or
  251. * node_access_example_edit. If the user has the 'access any private content'
  252. * permission we defined in hook_permission(), they're declared as belonging to
  253. * the node_access_example_realm. Similarly, if they have the 'edit any private
  254. * content' permission, we add the node_access_example_edit realm to the list
  255. * of grants they have.
  256. *
  257. * @see node_access_example_permission()
  258. * @see node_access_example_node_access_records()
  259. */
  260. function node_access_example_node_grants($account, $op) {
  261. $grants = array();
  262. // First grant a grant to the author for own content.
  263. // Do not grant to anonymous user else all anonymous users would be author.
  264. if ($account->uid) {
  265. $grants['node_access_example_author'] = array($account->uid);
  266. }
  267. // Then, if "access any private content" is allowed to the account,
  268. // grant view, update, or delete as necessary.
  269. if ($op == 'view' && user_access('access any private content', $account)) {
  270. $grants['node_access_example_view'] = array(NODE_ACCESS_EXAMPLE_GRANT_ALL);
  271. }
  272. if (($op == 'update' || $op == 'delete') && user_access('edit any private content', $account)) {
  273. $grants['node_access_example_edit'] = array(NODE_ACCESS_EXAMPLE_GRANT_ALL);
  274. }
  275. return $grants;
  276. }
  277. /**
  278. * Implements hook_node_access_records().
  279. *
  280. * All node access modules must implement this hook. If the module is
  281. * interested in the privacy of the node passed in, return a list
  282. * of node access values for each grant ID we offer.
  283. *
  284. * In this example, for each node which is marked 'private,' we define
  285. * three realms:
  286. *
  287. * The first and second are realms are 'node_access_example_view' and
  288. * 'node_access_example_edit,' which have a single grant ID, 1. The
  289. * user is either a member of these realms or not, depending upon the
  290. * operation and the access permission set.
  291. *
  292. * The third is node_access_example_author. It gives the node
  293. * author special privileges. node_access_example_author has one grant ID for
  294. * every UID, and each user is automatically a member of the group where
  295. * GID == UID. This has the effect of giving each user their own grant ID
  296. * for nodes they authored, within this realm.
  297. *
  298. * Drupal calls this hook when a node is saved, or when access permissions
  299. * change in order to rebuild the node access database table(s).
  300. *
  301. * The array you return will define the realm and the grant ID for the
  302. * given node. This is stored in the {node_access} table for subsequent
  303. * comparison against the user's realm and grant IDs, which you'll
  304. * supply in hook_node_grants().
  305. *
  306. * Realm names and grant IDs are arbitrary. Official drupal naming
  307. * conventions do not cover access realms, but since all realms are
  308. * stored in the same database table, it's probably a good idea to
  309. * use descriptive names which follow the module name, such as
  310. * 'mymodule_realmname'.
  311. *
  312. * @see node_access_example_node_grants()
  313. */
  314. function node_access_example_node_access_records($node) {
  315. // We only care about the node if it's been marked private. If not, it is
  316. // treated just like any other node and we completely ignore it.
  317. if (!empty($node->private)) {
  318. $grants = array();
  319. $grants[] = array(
  320. 'realm' => 'node_access_example_view',
  321. 'gid' => NODE_ACCESS_EXAMPLE_GRANT_ALL,
  322. 'grant_view' => 1,
  323. 'grant_update' => 0,
  324. 'grant_delete' => 0,
  325. 'priority' => 0,
  326. );
  327. $grants[] = array(
  328. 'realm' => 'node_access_example_edit',
  329. 'gid' => NODE_ACCESS_EXAMPLE_GRANT_ALL,
  330. 'grant_view' => 1,
  331. 'grant_update' => 1,
  332. 'grant_delete' => 1,
  333. 'priority' => 0,
  334. );
  335. // For the node_access_example_author realm, the grant ID (gid) is
  336. // equivalent to the node author's user ID (UID).
  337. // We check the node UID so that we don't grant author privileges for
  338. // anonymous nodes to anonymous users.
  339. if ($node->uid) {
  340. $grants[] = array(
  341. 'realm' => 'node_access_example_author',
  342. 'gid' => $node->uid,
  343. 'grant_view' => 1,
  344. 'grant_update' => 1,
  345. 'grant_delete' => 1,
  346. 'priority' => 0,
  347. );
  348. }
  349. return $grants;
  350. }
  351. // Return nothing if the node has not been marked private.
  352. }
  353. /**
  354. * Implements hook_form_alter().
  355. *
  356. * This module adds a simple checkbox to the node form labeled private. If the
  357. * checkbox is checked, only the node author and users with
  358. * 'access any private content' privileges may see it.
  359. */
  360. function node_access_example_form_alter(&$form, $form_state) {
  361. if (!empty($form['#node_edit_form'])) {
  362. $form['node_access_example'] = array(
  363. '#type' => 'fieldset',
  364. '#title' => t('Node Access Example'),
  365. '#collapsible' => TRUE,
  366. '#collapsed' => FALSE,
  367. '#weight' => 8,
  368. );
  369. $form['node_access_example']['private'] = array(
  370. '#type' => 'checkbox',
  371. '#title' => t('Private'),
  372. '#description' => t('Check here if this content should be set private and only shown to privileged users.'),
  373. '#default_value' => isset($form['#node']->private) ? $form['#node']->private : FALSE,
  374. );
  375. }
  376. }
  377. /**
  378. * Implements hook_node_load().
  379. *
  380. * Gather and add the private setting for the nodes Drupal is loading.
  381. * @see nodeapi_example.module
  382. */
  383. function node_access_example_node_load($nodes, $types) {
  384. $result = db_query('SELECT nid, private FROM {node_access_example} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)));
  385. foreach ($result as $record) {
  386. $nodes[$record->nid]->private = $record->private;
  387. }
  388. }
  389. /**
  390. * Implements hook_node_delete().
  391. *
  392. * Delete the node_access_example record when the node is deleted.
  393. * @see nodeapi_example.module
  394. */
  395. function node_access_example_node_delete($node) {
  396. db_delete('node_access_example')->condition('nid', $node->nid)->execute();
  397. }
  398. /**
  399. * Implements hook_node_insert().
  400. *
  401. * Insert a new access record when a node is created.
  402. * @see nodeapi_example.module
  403. */
  404. function node_access_example_node_insert($node) {
  405. if (isset($node->private)) {
  406. db_insert('node_access_example')->fields(
  407. array(
  408. 'nid' => $node->nid,
  409. 'private' => (int) $node->private,
  410. )
  411. )->execute();
  412. }
  413. drupal_set_message(t('New node @nid was created and private=@private', array('@nid' => $node->nid, '@private' => !empty($node->private) ? 1 : 0)));
  414. }
  415. /**
  416. * Implements hook_node_update().
  417. *
  418. * If the record in the node_access_example table already exists, we must
  419. * update it. If it doesn't exist, we create it.
  420. * @see nodeapi_example.module
  421. */
  422. function node_access_example_node_update($node) {
  423. // Find out if there is already a node_access_example record.
  424. $exists = db_query('SELECT nid FROM {node_access_example} WHERE nid = :nid',
  425. array(':nid' => $node->nid))->fetchField();
  426. // If there is already a record, update it with the new private value.
  427. if ($exists) {
  428. $num_updated = db_update('node_access_example')
  429. ->fields(array(
  430. 'nid' => $node->nid,
  431. 'private' => !empty($node->private) ? 1 : 0,
  432. ))
  433. ->condition('nid', $node->nid)
  434. ->execute();
  435. drupal_set_message(
  436. t("Updated node @nid to set private=@private (@num nodes actually updated)",
  437. array(
  438. '@private' => $node->private,
  439. '@num' => $num_updated,
  440. '@nid' => $node->nid,
  441. )
  442. )
  443. );
  444. }
  445. // Otherwise, create a new record.
  446. else {
  447. node_access_example_node_insert($node);
  448. drupal_set_message(t('Inserted new node_access nid=@nid, private=@private', array('@nid' => $node->nid, '@private' => $node->private)));
  449. }
  450. }
  451. /**
  452. * @} End of "defgroup node_access_example".
  453. */