node.api.php 17 KB

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