workflow_access.module 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <?php
  2. /**
  3. * @file
  4. * Provides node access permissions based on workflow states.
  5. */
  6. /**
  7. * Implements hook_menu().
  8. */
  9. function workflow_access_menu() {
  10. $items = array();
  11. $admin_path = WORKFLOW_ADMIN_UI_PATH;
  12. $id_count = count(explode('/', $admin_path));
  13. $items["$admin_path/access"] = array(
  14. 'title' => 'Access settings',
  15. 'file' => 'workflow_access.pages.inc',
  16. 'access arguments' => array('administer workflow'),
  17. 'page callback' => 'drupal_get_form',
  18. 'page arguments' => array('workflow_access_priority_form'),
  19. 'type' => MENU_LOCAL_ACTION,
  20. );
  21. $items["$admin_path/manage/%workflow/access"] = array(
  22. 'title' => 'Access',
  23. 'file' => 'workflow_access.pages.inc',
  24. 'access arguments' => array('administer workflow'),
  25. 'page callback' => 'drupal_get_form',
  26. 'page arguments' => array('workflow_access_form', $id_count + 1),
  27. 'type' => MENU_CALLBACK,
  28. );
  29. return $items;
  30. }
  31. /**
  32. * Implements hook_workflow_operations().
  33. */
  34. function workflow_access_workflow_operations($op, $workflow = NULL, $state = NULL) {
  35. $admin_path = WORKFLOW_ADMIN_UI_PATH;
  36. switch ($op) {
  37. case 'workflow':
  38. $actions = array();
  39. if ($workflow && $workflow->getStates()) {
  40. $wid = $workflow->getWorkflowId();
  41. $alt = t('Control content access for @wf', array('@wf' => $workflow->getName()));
  42. $actions += array(
  43. 'workflow_access_form' => array(
  44. 'title' => t('Access'),
  45. 'href' => "$admin_path/manage/$wid/access",
  46. 'attributes' => array('alt' => $alt, 'title' => $alt),
  47. ),
  48. );
  49. }
  50. else {
  51. // Generate a dummy, if no states exist.
  52. $actions = array(
  53. 'workflow_access_form' => array(
  54. 'title' => '',
  55. 'href' => '',
  56. ),
  57. );
  58. }
  59. return $actions;
  60. }
  61. }
  62. /**
  63. * Implements hook_help().
  64. */
  65. function workflow_access_help($path, $arg) {
  66. $help = '';
  67. switch ($path) {
  68. case WORKFLOW_ADMIN_UI_PATH . '/manage/%/access':
  69. $help = t("WARNING: Use of the 'Edit any', 'Edit own', and even 'View
  70. published content' permissions for the content type may override these
  71. access settings. You may need to <a href='!url'>alter the priority of
  72. the Workflow access module</a>.", array('!url' => url(WORKFLOW_ADMIN_UI_PATH . '/access'))
  73. );
  74. if (module_exists('og')) {
  75. $help .= '<br>';
  76. $help .= t('WARNING: Organic Groups (OG) is present and may interfere
  77. with these settings.');
  78. if (variable_get('og_node_access_strict', TRUE)) {
  79. $help .= ' ';
  80. $help .= t('In particular, <a href="!url">Strict node access
  81. permissions</a> is enabled and may override Workflow access
  82. settings.', array('!url' => url('admin/config/group/settings')));
  83. }
  84. }
  85. break;
  86. default:
  87. break;
  88. }
  89. return $help;
  90. }
  91. /**
  92. * Implements hook_features_api().
  93. *
  94. * Tell the Features module that we intend to provide one exportable component.
  95. */
  96. function workflow_access_features_api() {
  97. return array(
  98. 'workflow_access' => array(
  99. 'name' => t('Workflow access'),
  100. 'file' => drupal_get_path('module', 'workflow_access') . '/workflow_access.features.inc',
  101. 'default_hook' => 'workflow_access_features_default_settings',
  102. 'default_file' => FEATURES_DEFAULTS_INCLUDED,
  103. 'feature_source' => TRUE,
  104. ),
  105. );
  106. }
  107. /**
  108. * Implements hook_node_grants().
  109. *
  110. * Supply the workflow access grants. We are simply using
  111. * roles as access lists, so rids translate directly to gids.
  112. */
  113. function workflow_access_node_grants($account, $op) {
  114. return array(
  115. 'workflow_access' => array_keys($account->roles),
  116. 'workflow_access_owner' => array($account->uid),
  117. );
  118. }
  119. /**
  120. * Implements hook_node_access_records().
  121. *
  122. * Returns a list of grant records for the passed in node object.
  123. * This hook is invoked by function node_access_acquire_grants().
  124. */
  125. function workflow_access_node_access_records($node) {
  126. $grants = array();
  127. $workflow_transitions_added = FALSE;
  128. // Only relevant for content with Workflow.
  129. if (!isset($node->workflow_transitions)) {
  130. $node->workflow_transitions = array();
  131. // Sometimes, a node is saved without going through workflow_transition_form.
  132. // E.g.,
  133. // - when saving a node with workflow_node programmatically with node_save();
  134. // - when changing a state on a node view page/history tab;
  135. // - when rebuilding permissions via batch for workflow_node and workflow_field.
  136. // In that case, we need to create the workflow_transitions ourselves to
  137. // calculate the grants.
  138. foreach (_workflow_info_fields($node, 'node') as $field_name => $field) {
  139. $old_sid = FALSE;
  140. // Create a dummy transition, just to set $node->workflow_transitions.
  141. if (isset($node->workflow)) {
  142. $old_sid = $new_sid = $node->workflow;
  143. }
  144. elseif (isset($node->{$field_name}[LANGUAGE_NONE])) {
  145. $old_sid = $new_sid = _workflow_get_sid_by_items($node->{$field_name}[LANGUAGE_NONE]);
  146. }
  147. if ($old_sid) {
  148. $transition = new WorkflowTransition();
  149. $transition->setValues('node', $node, $field_name, $old_sid, $new_sid, $node->uid, REQUEST_TIME, '');
  150. // Store the transition, so it can be easily fetched later on.
  151. // Store in an array, to prepare for multiple workflow_fields per entity.
  152. // This is a.o. used in hook_entity_update to trigger 'transition post'.
  153. $node->workflow_transitions[$field_name] = $transition;
  154. $workflow_transitions_added = TRUE;
  155. }
  156. }
  157. }
  158. // Get 'author' of this entity.
  159. // - Some entities (e.g., taxonomy_term) do not have a uid.
  160. // But then again: node_access is only for nodes...
  161. $uid = isset($node->uid) ? $node->uid : 0;
  162. $count = 0;
  163. foreach ($node->workflow_transitions as $transition) {
  164. // @todo: add support for +1 workflows per entity.
  165. if ($count++ == 1 ) {
  166. continue;
  167. }
  168. $field_name = $transition->field_name;
  169. $priority = variable_get('workflow_access_priority', 0);
  170. if ($current_sid = workflow_node_current_state($node, 'node', $field_name)) {
  171. foreach (workflow_access_get_workflow_access_by_sid($current_sid) as $grant) {
  172. $realm = ($uid > 0 && $grant->rid == WORKFLOW_ROLE_AUTHOR_RID) ? 'workflow_access_owner' : 'workflow_access';
  173. $gid = ($uid > 0 && $grant->rid == WORKFLOW_ROLE_AUTHOR_RID) ? $uid : $grant->rid;
  174. // Anonymous ($uid == 0) author is not allowed for role 'author' (== -1).
  175. // Both logically (Anonymous having more rights then authenticated)
  176. // and technically: $gid must be a positive int.
  177. if ($gid < 0) { // if ($uid == 0 && $grant->rid == WORKFLOW_ROLE_AUTHOR_RID) {
  178. continue;
  179. }
  180. $grants[] = array(
  181. 'realm' => $realm,
  182. 'gid' => $gid,
  183. 'grant_view' => $grant->grant_view,
  184. 'grant_update' => $grant->grant_update,
  185. 'grant_delete' => $grant->grant_delete,
  186. 'priority' => $priority,
  187. 'field_name' => $field_name, // Just for analysis and info.
  188. );
  189. }
  190. }
  191. }
  192. if ($workflow_transitions_added == TRUE) {
  193. unset($node->workflow_transitions);
  194. }
  195. return $grants;
  196. }
  197. /**
  198. * Implements hook_node_access_explain().
  199. *
  200. * This is a Devel Node Access hook.
  201. */
  202. function workflow_access_node_access_explain($row) {
  203. static $interpretations = array();
  204. switch ($row->realm) {
  205. case 'workflow_access_owner':
  206. $interpretations[$row->gid] = t('Workflow access: author of the content may access');
  207. break;
  208. case 'workflow_access':
  209. $roles = user_roles();
  210. $interpretations[$row->gid] = t('Workflow access: %role may access', array('%role' => $roles[$row->gid]));
  211. break;
  212. }
  213. return (!empty($interpretations[$row->gid]) ? $interpretations[$row->gid] : NULL);
  214. }
  215. /**
  216. * DB functions - all DB interactions are isolated here to make for easy updating should our schema change.
  217. */
  218. /**
  219. * Given a sid, retrieve the access information and return the row(s).
  220. */
  221. function workflow_access_get_workflow_access_by_sid($sid) {
  222. $results = db_query('SELECT * from {workflow_access} where sid = :sid', array(':sid' => $sid));
  223. return $results->fetchAll();
  224. }
  225. /**
  226. * Given a sid and rid (the unique key), delete all access data for this state.
  227. */
  228. function workflow_access_delete_workflow_access_by_sid_rid($sid, $rid) {
  229. db_delete('workflow_access')->condition('sid', $sid)->condition('rid', $rid)->execute();
  230. }
  231. /**
  232. * Given a sid, delete all access data for this state.
  233. */
  234. function workflow_access_delete_workflow_access_by_sid($sid) {
  235. db_delete('workflow_access')->condition('sid', $sid)->execute();
  236. }
  237. /**
  238. * Given data, insert into workflow access - we never update.
  239. */
  240. function workflow_access_insert_workflow_access_by_sid(&$data) {
  241. $data = (object) $data;
  242. workflow_access_delete_workflow_access_by_sid_rid($data->sid, $data->rid);
  243. drupal_write_record('workflow_access', $data);
  244. }