node.api.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. <?php
  2. /**
  3. * @file
  4. * Hooks specific to the Node module.
  5. */
  6. use Drupal\node\NodeInterface;
  7. use Drupal\Component\Utility\Html;
  8. use Drupal\Component\Utility\Xss;
  9. use Drupal\Core\Access\AccessResult;
  10. /**
  11. * @addtogroup hooks
  12. * @{
  13. */
  14. /**
  15. * Inform the node access system what permissions the user has.
  16. *
  17. * This hook is for implementation by node access modules. In this hook,
  18. * the module grants a user different "grant IDs" within one or more
  19. * "realms". In hook_node_access_records(), the realms and grant IDs are
  20. * associated with permission to view, edit, and delete individual nodes.
  21. *
  22. * The realms and grant IDs can be arbitrarily defined by your node access
  23. * module; it is common to use role IDs as grant IDs, but that is not required.
  24. * Your module could instead maintain its own list of users, where each list has
  25. * an ID. In that case, the return value of this hook would be an array of the
  26. * list IDs that this user is a member of.
  27. *
  28. * A node access module may implement as many realms as necessary to properly
  29. * define the access privileges for the nodes. Note that the system makes no
  30. * distinction between published and unpublished nodes. It is the module's
  31. * responsibility to provide appropriate realms to limit access to unpublished
  32. * content.
  33. *
  34. * Node access records are stored in the {node_access} table and define which
  35. * grants are required to access a node. There is a special case for the view
  36. * operation -- a record with node ID 0 corresponds to a "view all" grant for
  37. * the realm and grant ID of that record. If there are no node access modules
  38. * enabled, the core node module adds a node ID 0 record for realm 'all'. Node
  39. * access modules can also grant "view all" permission on their custom realms;
  40. * for example, a module could create a record in {node_access} with:
  41. * @code
  42. * $record = array(
  43. * 'nid' => 0,
  44. * 'gid' => 888,
  45. * 'realm' => 'example_realm',
  46. * 'grant_view' => 1,
  47. * 'grant_update' => 0,
  48. * 'grant_delete' => 0,
  49. * );
  50. * db_insert('node_access')->fields($record)->execute();
  51. * @endcode
  52. * And then in its hook_node_grants() implementation, it would need to return:
  53. * @code
  54. * if ($op == 'view') {
  55. * $grants['example_realm'] = array(888);
  56. * }
  57. * @endcode
  58. * If you decide to do this, be aware that the node_access_rebuild() function
  59. * will erase any node ID 0 entry when it is called, so you will need to make
  60. * sure to restore your {node_access} record after node_access_rebuild() is
  61. * called.
  62. *
  63. * For a detailed example, see node_access_example.module.
  64. *
  65. * @param \Drupal\Core\Session\AccountInterface $account
  66. * The account object whose grants are requested.
  67. * @param string $op
  68. * The node operation to be performed, such as 'view', 'update', or 'delete'.
  69. *
  70. * @return array
  71. * An array whose keys are "realms" of grants, and whose values are arrays of
  72. * the grant IDs within this realm that this user is being granted.
  73. *
  74. * @see node_access_view_all_nodes()
  75. * @see node_access_rebuild()
  76. * @ingroup node_access
  77. */
  78. function hook_node_grants(\Drupal\Core\Session\AccountInterface $account, $op) {
  79. if ($account->hasPermission('access private content')) {
  80. $grants['example'] = [1];
  81. }
  82. if ($account->id()) {
  83. $grants['example_author'] = [$account->id()];
  84. }
  85. return $grants;
  86. }
  87. /**
  88. * Set permissions for a node to be written to the database.
  89. *
  90. * When a node is saved, a module implementing hook_node_access_records() will
  91. * be asked if it is interested in the access permissions for a node. If it is
  92. * interested, it must respond with an array of permissions arrays for that
  93. * node.
  94. *
  95. * Node access grants apply regardless of the published or unpublished status
  96. * of the node. Implementations must make sure not to grant access to
  97. * unpublished nodes if they don't want to change the standard access control
  98. * behavior. Your module may need to create a separate access realm to handle
  99. * access to unpublished nodes.
  100. *
  101. * Note that the grant values in the return value from your hook must be
  102. * integers and not boolean TRUE and FALSE.
  103. *
  104. * Each permissions item in the array is an array with the following elements:
  105. * - 'realm': The name of a realm that the module has defined in
  106. * hook_node_grants().
  107. * - 'gid': A 'grant ID' from hook_node_grants().
  108. * - 'grant_view': If set to 1 a user that has been identified as a member
  109. * of this gid within this realm can view this node. This should usually be
  110. * set to $node->isPublished(). Failure to do so may expose unpublished content
  111. * to some users.
  112. * - 'grant_update': If set to 1 a user that has been identified as a member
  113. * of this gid within this realm can edit this node.
  114. * - 'grant_delete': If set to 1 a user that has been identified as a member
  115. * of this gid within this realm can delete this node.
  116. * - langcode: (optional) The language code of a specific translation of the
  117. * node, if any. Modules may add this key to grant different access to
  118. * different translations of a node, such that (e.g.) a particular group is
  119. * granted access to edit the Catalan version of the node, but not the
  120. * Hungarian version. If no value is provided, the langcode is set
  121. * automatically from the $node parameter and the node's original language (if
  122. * specified) is used as a fallback. Only specify multiple grant records with
  123. * different languages for a node if the site has those languages configured.
  124. *
  125. * A "deny all" grant may be used to deny all access to a particular node or
  126. * node translation:
  127. * @code
  128. * $grants[] = array(
  129. * 'realm' => 'all',
  130. * 'gid' => 0,
  131. * 'grant_view' => 0,
  132. * 'grant_update' => 0,
  133. * 'grant_delete' => 0,
  134. * 'langcode' => 'ca',
  135. * );
  136. * @endcode
  137. * Note that another module node access module could override this by granting
  138. * access to one or more nodes, since grants are additive. To enforce that
  139. * access is denied in a particular case, use hook_node_access_records_alter().
  140. * Also note that a deny all is not written to the database; denies are
  141. * implicit.
  142. *
  143. * @param \Drupal\node\NodeInterface $node
  144. * The node that has just been saved.
  145. *
  146. * @return
  147. * An array of grants as defined above.
  148. *
  149. * @see hook_node_access_records_alter()
  150. * @ingroup node_access
  151. */
  152. function hook_node_access_records(\Drupal\node\NodeInterface $node) {
  153. // We only care about the node if it has been marked private. If not, it is
  154. // treated just like any other node and we completely ignore it.
  155. if ($node->private->value) {
  156. $grants = [];
  157. // Only published Catalan translations of private nodes should be viewable
  158. // to all users. If we fail to check $node->isPublished(), all users would be able
  159. // to view an unpublished node.
  160. if ($node->isPublished()) {
  161. $grants[] = [
  162. 'realm' => 'example',
  163. 'gid' => 1,
  164. 'grant_view' => 1,
  165. 'grant_update' => 0,
  166. 'grant_delete' => 0,
  167. 'langcode' => 'ca'
  168. ];
  169. }
  170. // For the example_author array, the GID is equivalent to a UID, which
  171. // means there are many groups of just 1 user.
  172. // Note that an author can always view his or her nodes, even if they
  173. // have status unpublished.
  174. if ($node->getOwnerId()) {
  175. $grants[] = [
  176. 'realm' => 'example_author',
  177. 'gid' => $node->getOwnerId(),
  178. 'grant_view' => 1,
  179. 'grant_update' => 1,
  180. 'grant_delete' => 1,
  181. 'langcode' => 'ca'
  182. ];
  183. }
  184. return $grants;
  185. }
  186. }
  187. /**
  188. * Alter permissions for a node before it is written to the database.
  189. *
  190. * Node access modules establish rules for user access to content. Node access
  191. * records are stored in the {node_access} table and define which permissions
  192. * are required to access a node. This hook is invoked after node access modules
  193. * returned their requirements via hook_node_access_records(); doing so allows
  194. * modules to modify the $grants array by reference before it is stored, so
  195. * custom or advanced business logic can be applied.
  196. *
  197. * Upon viewing, editing or deleting a node, hook_node_grants() builds a
  198. * permissions array that is compared against the stored access records. The
  199. * user must have one or more matching permissions in order to complete the
  200. * requested operation.
  201. *
  202. * A module may deny all access to a node by setting $grants to an empty array.
  203. *
  204. * The preferred use of this hook is in a module that bridges multiple node
  205. * access modules with a configurable behavior, as shown in the example with the
  206. * 'is_preview' field.
  207. *
  208. * @param array $grants
  209. * The $grants array returned by hook_node_access_records().
  210. * @param \Drupal\node\NodeInterface $node
  211. * The node for which the grants were acquired.
  212. *
  213. * @see hook_node_access_records()
  214. * @see hook_node_grants()
  215. * @see hook_node_grants_alter()
  216. * @ingroup node_access
  217. */
  218. function hook_node_access_records_alter(&$grants, Drupal\node\NodeInterface $node) {
  219. // Our module allows editors to mark specific articles with the 'is_preview'
  220. // field. If the node being saved has a TRUE value for that field, then only
  221. // our grants are retained, and other grants are removed. Doing so ensures
  222. // that our rules are enforced no matter what priority other grants are given.
  223. if ($node->is_preview) {
  224. // Our module grants are set in $grants['example'].
  225. $temp = $grants['example'];
  226. // Now remove all module grants but our own.
  227. $grants = ['example' => $temp];
  228. }
  229. }
  230. /**
  231. * Alter user access rules when trying to view, edit or delete a node.
  232. *
  233. * Node access modules establish rules for user access to content.
  234. * hook_node_grants() defines permissions for a user to view, edit or delete
  235. * nodes by building a $grants array that indicates the permissions assigned to
  236. * the user by each node access module. This hook is called to allow modules to
  237. * modify the $grants array by reference, so the interaction of multiple node
  238. * access modules can be altered or advanced business logic can be applied.
  239. *
  240. * The resulting grants are then checked against the records stored in the
  241. * {node_access} table to determine if the operation may be completed.
  242. *
  243. * A module may deny all access to a user by setting $grants to an empty array.
  244. *
  245. * Developers may use this hook to either add additional grants to a user or to
  246. * remove existing grants. These rules are typically based on either the
  247. * permissions assigned to a user role, or specific attributes of a user
  248. * account.
  249. *
  250. * @param array $grants
  251. * The $grants array returned by hook_node_grants().
  252. * @param \Drupal\Core\Session\AccountInterface $account
  253. * The account requesting access to content.
  254. * @param string $op
  255. * The operation being performed, 'view', 'update' or 'delete'.
  256. *
  257. * @see hook_node_grants()
  258. * @see hook_node_access_records()
  259. * @see hook_node_access_records_alter()
  260. * @ingroup node_access
  261. */
  262. function hook_node_grants_alter(&$grants, \Drupal\Core\Session\AccountInterface $account, $op) {
  263. // Our sample module never allows certain roles to edit or delete
  264. // content. Since some other node access modules might allow this
  265. // permission, we expressly remove it by returning an empty $grants
  266. // array for roles specified in our variable setting.
  267. // Get our list of banned roles.
  268. $restricted = \Drupal::config('example.settings')->get('restricted_roles');
  269. if ($op != 'view' && !empty($restricted)) {
  270. // Now check the roles for this account against the restrictions.
  271. foreach ($account->getRoles() as $rid) {
  272. if (in_array($rid, $restricted)) {
  273. $grants = [];
  274. }
  275. }
  276. }
  277. }
  278. /**
  279. * Controls access to a node.
  280. *
  281. * Modules may implement this hook if they want to have a say in whether or not
  282. * a given user has access to perform a given operation on a node.
  283. *
  284. * The administrative account (user ID #1) always passes any access check, so
  285. * this hook is not called in that case. Users with the "bypass node access"
  286. * permission may always view and edit content through the administrative
  287. * interface.
  288. *
  289. * Note that not all modules will want to influence access on all node types. If
  290. * your module does not want to explicitly allow or forbid access, return an
  291. * AccessResultInterface object with neither isAllowed() nor isForbidden()
  292. * equaling TRUE. Blindly returning an object with isForbidden() equaling TRUE
  293. * will break other node access modules.
  294. *
  295. * Also note that this function isn't called for node listings (e.g., RSS feeds,
  296. * the default home page at path 'node', a recent content block, etc.) See
  297. * @link node_access Node access rights @endlink for a full explanation.
  298. *
  299. * @param \Drupal\node\NodeInterface|string $node
  300. * Either a node entity or the machine name of the content type on which to
  301. * perform the access check.
  302. * @param string $op
  303. * The operation to be performed. Possible values:
  304. * - "create"
  305. * - "delete"
  306. * - "update"
  307. * - "view"
  308. * @param \Drupal\Core\Session\AccountInterface $account
  309. * The user object to perform the access check operation on.
  310. *
  311. * @return \Drupal\Core\Access\AccessResultInterface
  312. * The access result.
  313. *
  314. * @ingroup node_access
  315. */
  316. function hook_node_access(\Drupal\node\NodeInterface $node, $op, \Drupal\Core\Session\AccountInterface $account) {
  317. $type = $node->bundle();
  318. switch ($op) {
  319. case 'create':
  320. return AccessResult::allowedIfHasPermission($account, 'create ' . $type . ' content');
  321. case 'update':
  322. if ($account->hasPermission('edit any ' . $type . ' content', $account)) {
  323. return AccessResult::allowed()->cachePerPermissions();
  324. }
  325. else {
  326. return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->addCacheableDependency($node);
  327. }
  328. case 'delete':
  329. if ($account->hasPermission('delete any ' . $type . ' content', $account)) {
  330. return AccessResult::allowed()->cachePerPermissions();
  331. }
  332. else {
  333. return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->addCacheableDependency($node);
  334. }
  335. default:
  336. // No opinion.
  337. return AccessResult::neutral();
  338. }
  339. }
  340. /**
  341. * Act on a node being displayed as a search result.
  342. *
  343. * This hook is invoked from the node search plugin during search execution,
  344. * after loading and rendering the node.
  345. *
  346. * @param \Drupal\node\NodeInterface $node
  347. * The node being displayed in a search result.
  348. *
  349. * @return array
  350. * Extra information to be displayed with search result. This information
  351. * should be presented as an associative array. It will be concatenated with
  352. * the post information (last updated, author) in the default search result
  353. * theming.
  354. *
  355. * @see template_preprocess_search_result()
  356. * @see search-result.html.twig
  357. *
  358. * @ingroup entity_crud
  359. */
  360. function hook_node_search_result(\Drupal\node\NodeInterface $node) {
  361. $rating = db_query('SELECT SUM(points) FROM {my_rating} WHERE nid = :nid', ['nid' => $node->id()])->fetchField();
  362. return ['rating' => \Drupal::translation()->formatPlural($rating, '1 point', '@count points')];
  363. }
  364. /**
  365. * Act on a node being indexed for searching.
  366. *
  367. * This hook is invoked during search indexing, after loading, and after the
  368. * result of rendering is added as $node->rendered to the node object.
  369. *
  370. * @param \Drupal\node\NodeInterface $node
  371. * The node being indexed.
  372. *
  373. * @return string
  374. * Additional node information to be indexed.
  375. *
  376. * @ingroup entity_crud
  377. */
  378. function hook_node_update_index(\Drupal\node\NodeInterface $node) {
  379. $text = '';
  380. $ratings = db_query('SELECT title, description FROM {my_ratings} WHERE nid = :nid', [':nid' => $node->id()]);
  381. foreach ($ratings as $rating) {
  382. $text .= '<h2>' . Html::escape($rating->title) . '</h2>' . Xss::filter($rating->description);
  383. }
  384. return $text;
  385. }
  386. /**
  387. * Provide additional methods of scoring for core search results for nodes.
  388. *
  389. * A node's search score is used to rank it among other nodes matched by the
  390. * search, with the highest-ranked nodes appearing first in the search listing.
  391. *
  392. * For example, a module allowing users to vote on content could expose an
  393. * option to allow search results' rankings to be influenced by the average
  394. * voting score of a node.
  395. *
  396. * All scoring mechanisms are provided as options to site administrators, and
  397. * may be tweaked based on individual sites or disabled altogether if they do
  398. * not make sense. Individual scoring mechanisms, if enabled, are assigned a
  399. * weight from 1 to 10. The weight represents the factor of magnification of
  400. * the ranking mechanism, with higher-weighted ranking mechanisms having more
  401. * influence. In order for the weight system to work, each scoring mechanism
  402. * must return a value between 0 and 1 for every node. That value is then
  403. * multiplied by the administrator-assigned weight for the ranking mechanism,
  404. * and then the weighted scores from all ranking mechanisms are added, which
  405. * brings about the same result as a weighted average.
  406. *
  407. * @return array
  408. * An associative array of ranking data. The keys should be strings,
  409. * corresponding to the internal name of the ranking mechanism, such as
  410. * 'recent', or 'comments'. The values should be arrays themselves, with the
  411. * following keys available:
  412. * - title: (required) The human readable name of the ranking mechanism.
  413. * - join: (optional) An array with information to join any additional
  414. * necessary table. This is not necessary if the table required is already
  415. * joined to by the base query, such as for the {node} table. Other tables
  416. * should use the full table name as an alias to avoid naming collisions.
  417. * - score: (required) The part of a query string to calculate the score for
  418. * the ranking mechanism based on values in the database. This does not need
  419. * to be wrapped in parentheses, as it will be done automatically; it also
  420. * does not need to take the weighted system into account, as it will be
  421. * done automatically. It does, however, need to calculate a decimal between
  422. * 0 and 1; be careful not to cast the entire score to an integer by
  423. * inadvertently introducing a variable argument.
  424. * - arguments: (optional) If any arguments are required for the score, they
  425. * can be specified in an array here.
  426. *
  427. * @ingroup entity_crud
  428. */
  429. function hook_ranking() {
  430. // If voting is disabled, we can avoid returning the array, no hard feelings.
  431. if (\Drupal::config('vote.settings')->get('node_enabled')) {
  432. return [
  433. 'vote_average' => [
  434. 'title' => t('Average vote'),
  435. // Note that we use i.sid, the search index's search item id, rather than
  436. // n.nid.
  437. 'join' => [
  438. 'type' => 'LEFT',
  439. 'table' => 'vote_node_data',
  440. 'alias' => 'vote_node_data',
  441. 'on' => 'vote_node_data.nid = i.sid',
  442. ],
  443. // The highest possible score should be 1, and the lowest possible score,
  444. // always 0, should be 0.
  445. 'score' => 'vote_node_data.average / CAST(%f AS DECIMAL)',
  446. // Pass in the highest possible voting score as a decimal argument.
  447. 'arguments' => [\Drupal::config('vote.settings')->get('score_max')],
  448. ],
  449. ];
  450. }
  451. }
  452. /**
  453. * Alter the links of a node.
  454. *
  455. * @param array &$links
  456. * A renderable array representing the node links.
  457. * @param \Drupal\node\NodeInterface $entity
  458. * The node being rendered.
  459. * @param array &$context
  460. * Various aspects of the context in which the node links are going to be
  461. * displayed, with the following keys:
  462. * - 'view_mode': the view mode in which the node is being viewed
  463. * - 'langcode': the language in which the node is being viewed
  464. *
  465. * @see \Drupal\node\NodeViewBuilder::renderLinks()
  466. * @see \Drupal\node\NodeViewBuilder::buildLinks()
  467. * @see entity_crud
  468. */
  469. function hook_node_links_alter(array &$links, NodeInterface $entity, array &$context) {
  470. $links['mymodule'] = [
  471. '#theme' => 'links__node__mymodule',
  472. '#attributes' => ['class' => ['links', 'inline']],
  473. '#links' => [
  474. 'node-report' => [
  475. 'title' => t('Report'),
  476. 'url' => Url::fromRoute('node_test.report', ['node' => $entity->id()], ['query' => ['token' => \Drupal::getContainer()->get('csrf_token')->get("node/{$entity->id()}/report")]]),
  477. ],
  478. ],
  479. ];
  480. }
  481. /**
  482. * @} End of "addtogroup hooks".
  483. */