'Access', 'access arguments' => array('administer workflow'), 'page callback' => 'drupal_get_form', 'page arguments' => array('workflow_access_form', 4), 'type' => MENU_CALLBACK, ); return $items; } /** * Implements hook_node_grants(). * * Supply the workflow access grants. We are simply using * roles as access lists, so rids translate directly to gids. */ function workflow_access_node_grants($account, $op) { return array( 'workflow_access' => array_keys($account->roles), 'workflow_access_owner' => array($account->uid), ); } /** * Implements hook_node_access_records(). * * Returns a list of grant records for the passed in node object. */ function workflow_access_node_access_records($node) { $grants = array(); if ($state = workflow_get_workflow_node_by_nid($node->nid)) { $state = workflow_get_workflow_states_by_sid($state->sid); foreach (workflow_access_get_workflow_access_by_sid($state->sid) as $grant) { $grants[] = array( 'realm' => ($grant->rid == -1) ? 'workflow_access_owner' : 'workflow_access', 'gid' => ($grant->rid == -1) ? $node->uid : $grant->rid, 'grant_view' => $grant->grant_view, 'grant_update' => $grant->grant_update, 'grant_delete' => $grant->grant_delete, 'priority' => variable_get('workflow_access_priority', 0), ); } } return $grants; } /** * Implements hook_node_access_explain(). * This is a Devel Node Access hook. */ function workflow_access_node_access_explain($row) { static $interpretations = array(); switch ($row->realm) { case 'workflow_access_owner': $interpretations[$row->gid] = t('Workflow access: author of the content may access'); break; case 'workflow_access': $roles = user_roles(); $interpretations[$row->gid] = t('Workflow access: %role may access', array('%role' => $roles[$row->gid])); break; } return (!empty($interpretations[$row->gid]) ? $interpretations[$row->gid] : NULL); } /** * Implements hook_workflow_operations(). * Create action link for access form. */ function workflow_access_workflow_operations($op, $workflow = NULL, $state = NULL) { switch ($op) { case 'workflow': $alt = t('Control content access for @wf', array('@wf' => $workflow->name)); $actions = array( 'workflow_access_form' => array( 'title' => t('Access'), 'href' => "admin/config/workflow/access/$workflow->wid", 'attributes' => array('alt' => $alt, 'title' => $alt), ), ); return $actions; } } /** * Implements hook_form(). * * Add a "three dimensional" (state, role, permission type) configuration * interface to the workflow edit form. */ function workflow_access_form($form, $form_state, $workflow) { if ($workflow) { drupal_set_title(t('@name Access', array('@name' => $workflow->name))); } else { drupal_set_message(t('Unknown workflow')); drupal_goto('admin/config/workflow/workflow'); } $bc = array(l(t('Home'), '')); $bc[] = l(t('Configuration'), 'admin/config'); $bc[] = l(t('Workflow'), 'admin/config/workflow'); $bc[] = l(t('Workflow'), 'admin/config/workflow/workflow'); $bc[] = l(t($workflow->name), "admin/config/workflow/workflow/$workflow->wid"); // $bc[] = l(t('Access'), "admin/config/workflow/access/$workflow->wid"); drupal_set_breadcrumb($bc); $form = array('#tree' => TRUE); $form['#wid'] = $workflow->wid; // A list of roles available on the site and our // special -1 role used to represent the node author. $rids = user_roles(FALSE, 'participate in workflow'); $rids['-1'] = t('author'); // Add a table for every workflow state. $options = array('status' => 1); foreach (workflow_get_workflow_states_by_wid($workflow->wid, $options) as $state) { if ($state->state == t('(creation)')) { // No need to set perms on creation. continue; } $view = $update = $delete = array(); $count = 0; foreach (workflow_access_get_workflow_access_by_sid($state->sid) as $access) { $count++; if ($access->grant_view) { $view[] = $access->rid; } if ($access->grant_update) { $update[] = $access->rid; } if ($access->grant_delete) { $delete[] = $access->rid; } } // Allow view grants by default for anonymous and authenticated users, // if no grants were set up earlier. if (!$count) { $view = array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID); } // TODO better tables using a #theme function instead of direct #prefixing $form[$state->sid] = array( '#type' => 'fieldset', '#title' => t('@state', array('@state' => $state->state)), '#collapsible' => TRUE, '#collapsed' => FALSE, '#tree' => TRUE, ); $form[$state->sid]['view'] = array( '#type' => 'checkboxes', '#options' => $rids, '#default_value' => $view, '#title' => t('Roles who can view posts in this state'), '#prefix' => '
', ); $form[$state->sid]['update'] = array( '#type' => 'checkboxes', '#options' => $rids, '#default_value' => $update, '#title' => t('Roles who can edit posts in this state'), '#prefix' => "", ); $form[$state->sid]['delete'] = array( '#type' => 'checkboxes', '#options' => $rids, '#default_value' => $delete, '#title' => t('Roles who can delete posts in this state'), '#prefix' => "", '#suffix' => "
", ); } $form['warning'] = array( '#type' => 'markup', '#markup' => '

' . t('WARNING:') . ' ' . t('Use of the "Edit any," "Edit own," and even "View published content" permissions for the content type may override these access settings.') . '

', ); $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); return $form; // Place our block comfortably down the page. $form['submit']['#weight'] = 10; $form['#submit'][] = 'workflow_access_form_submit'; } /** * Store permission settings for workflow states. */ function workflow_access_form_submit($form, &$form_state) { foreach ($form_state['values'] as $sid => $access) { // Ignore irrelevant keys. if (!is_numeric($sid)) { continue; } $grants = array(); foreach ($access['view'] as $rid => $checked) { $data = array( 'sid' => $sid, 'rid' => $rid, 'grant_view' => (!empty($checked)) ? (bool) $checked : 0, 'grant_update' => (!empty($access['update'][$rid])) ? (bool) $access['update'][$rid] : 0, 'grant_delete' => (!empty($access['delete'][$rid])) ? (bool) $access['delete'][$rid] : 0, ); $id = workflow_access_insert_workflow_access_by_sid($data); } // Update all nodes having same workflow state to reflect new settings. foreach (workflow_get_workflow_node_by_sid($sid) as $data) { // Instead of trying to construct what the grants should be per node as we save. // Let's fall back on existing node grant systems that will find it for us. $node = node_load($data->nid); node_access_acquire_grants($node); } } drupal_set_message(t('Workflow access permissions updated.')); $form_state['redirect'] = 'admin/config/workflow/workflow/' . $form['#wid']; } /** * Implements hook_workflow(). * * Update grants when a node changes workflow state. * This is already called when node_save is called. */ function workflow_access_workflow($op, $old_sid, $sid, $node) { // ALERT: // This is a tricky spot when called on node_insert as part of the transition from create to state1. // node_save invokes this function as a hook before calling node_access_acquire_grants. // But when it calls node_access_acquire_grants later, it does so without deleting the access // set when calling workflow_node_insert because it is an insert and no prior grants are expected. // This leads to a SQL error of duplicate grants which causes a rollback of all changes. // Unfortunately, setting access rights isn't the only thing we're doing on node_insert so we // can't skip the whole thing. So we need to fix it further downstream in order to get this to work. // Here we don't want to run this in the case of (and ONLY in the case of) a brand new node. // Node access grants will be run as part of node_save's own granting after this. // // NOTE: Any module that sets node access rights on insert will hit this situation. // if ($old_state = workflow_get_workflow_states_by_sid($old_sid)) { if ($op == 'transition post' && $old_sid != $sid && (empty($node->is_new) || !$node->is_new)) { node_access_acquire_grants($node); } } } /** * DB functions - all DB interactions are isolated here to make for easy updating should our schema change. */ /** * Given a sid, retrieve the access information and return the row(s). */ function workflow_access_get_workflow_access_by_sid($sid) { $results = db_query('SELECT * from {workflow_access} where sid = :sid', array(':sid' => $sid)); return $results->fetchAll(); } /** * Given a sid and a rid (the unique key), delete all access data for this state. */ function workflow_access_delete_workflow_access_by_sid_rid($sid, $rid) { db_delete('workflow_access')->condition('sid', $sid)->condition('rid', $rid)->execute(); } /** * Given a sid, delete all access data for this state. */ function workflow_access_delete_workflow_access_by_sid($sid) { db_delete('workflow_access')->condition('sid', $sid)->execute(); } /** * Given data, insert into workflow access - we never update. */ function workflow_access_insert_workflow_access_by_sid(&$data) { $data = (object) $data; workflow_access_delete_workflow_access_by_sid_rid($data->sid, $data->rid); drupal_write_record('workflow_access', $data); } /** * Implements hook_form_alter(). * * Tell the Features module that we intend to provide one exportable component. */ function workflow_access_form_alter(&$form, &$form_state, $form_id) { switch ($form_id) { case 'workflow_admin_ui_types_form': $form['workflow_access'] = array( '#type' => 'fieldset', '#title' => t('Workflow Access'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); $form['workflow_access']['workflow_access_priority'] = array( '#type' => 'weight', '#delta' => 10, '#title' => t('Workflow Access Priority'), '#default_value' => variable_get('workflow_access_priority', 0), '#description' => t('This sets the node access priority. This can be dangerous. If there is any doubt, leave it at 0.') . ' ' . l(t('Read the manual.'), 'https://api.drupal.org/api/drupal/modules!node!node.api.php/function/hook_node_access_records/7'), ); $form['#submit'][] = 'workflow_access_priority_submit'; return; } } /** * Submit handler. */ function workflow_access_priority_submit($form, &$form_state) { variable_set('workflow_access_priority', $form_state['values']['workflow_access']['workflow_access_priority']); } /** * Implements hook_features_api(). * * Tell the Features module that we intend to provide one exportable component. */ function workflow_access_features_api() { return array( 'workflow_access' => array( 'name' => t('Workflow access'), 'file' => drupal_get_path('module', 'workflow_access') . '/workflow_access.features.inc', 'default_hook' => 'workflow_access_features_default_settings', 'default_file' => FEATURES_DEFAULTS_INCLUDED, 'feature_source' => TRUE, ), ); }